summaryrefslogtreecommitdiff
path: root/src/yuzu/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu/main.cpp')
-rw-r--r--src/yuzu/main.cpp647
1 files changed, 541 insertions, 106 deletions
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 3c2d7d080..4146ebc2c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1,14 +1,18 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
+// SPDX-FileCopyrightText: 2014 Citra Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
#include <cinttypes>
#include <clocale>
+#include <cmath>
#include <memory>
#include <thread>
#ifdef __APPLE__
#include <unistd.h> // for chdir
#endif
+#ifdef __linux__
+#include <csignal>
+#include <sys/socket.h>
+#endif
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/qt_controller.h"
@@ -20,11 +24,11 @@
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h"
-#include "configuration/configure_vibration.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/controller.h"
#include "core/frontend/applets/general_frontend.h"
+#include "core/frontend/applets/mii_edit.h"
#include "core/frontend/applets/software_keyboard.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_core.h"
@@ -32,6 +36,7 @@
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/applets/applets.h"
+#include "yuzu/multiplayer/state.h"
#include "yuzu/util/controller_navigation.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
@@ -52,9 +57,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QClipboard>
#include <QDesktopServices>
-#include <QDesktopWidget>
-#include <QDialogButtonBox>
-#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QInputDialog>
@@ -62,6 +64,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <QProgressBar>
#include <QProgressDialog>
#include <QPushButton>
+#include <QScreen>
#include <QShortcut>
#include <QStatusBar>
#include <QString>
@@ -76,11 +79,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include <fmt/format.h>
#include "common/detached_tasks.h"
#include "common/fs/fs.h"
-#include "common/fs/fs_paths.h"
#include "common/fs/path_util.h"
#include "common/literals.h"
#include "common/logging/backend.h"
-#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/memory_detect.h"
#include "common/microprofile.h"
@@ -105,12 +106,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
-#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
#include "input_common/drivers/tas_input.h"
+#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/main.h"
#include "ui_main.h"
#include "util/overlay_dialog.h"
@@ -134,7 +135,13 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "yuzu/install_dialog.h"
#include "yuzu/loading_screen.h"
#include "yuzu/main.h"
+#include "yuzu/startup_checks.h"
#include "yuzu/uisettings.h"
+#include "yuzu/util/clickable_label.h"
+
+#ifdef YUZU_DBGHELP
+#include "yuzu/mini_dump.h"
+#endif
using namespace Common::Literals;
@@ -156,7 +163,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
-constexpr int default_mouse_timeout = 2500;
+constexpr int default_mouse_hide_timeout = 2500;
+constexpr int default_mouse_center_timeout = 10;
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
@@ -201,12 +209,80 @@ static void RemoveCachedContents() {
Common::FS::RemoveDirRecursively(offline_system_data);
}
-GMainWindow::GMainWindow()
+static void LogRuntimes() {
+#ifdef _MSC_VER
+ // It is possible that the name of the dll will change.
+ // vcruntime140.dll is for 2015 and onwards
+ constexpr char runtime_dll_name[] = "vcruntime140.dll";
+ UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr);
+ bool runtime_version_inspection_worked = false;
+ if (sz > 0) {
+ std::vector<u8> buf(sz);
+ if (GetFileVersionInfoA(runtime_dll_name, 0, sz, buf.data())) {
+ VS_FIXEDFILEINFO* pvi;
+ sz = sizeof(VS_FIXEDFILEINFO);
+ if (VerQueryValueA(buf.data(), "\\", reinterpret_cast<LPVOID*>(&pvi), &sz)) {
+ if (pvi->dwSignature == VS_FFI_SIGNATURE) {
+ runtime_version_inspection_worked = true;
+ LOG_INFO(Frontend, "MSVC Compiler: {} Runtime: {}.{}.{}.{}", _MSC_VER,
+ pvi->dwProductVersionMS >> 16, pvi->dwProductVersionMS & 0xFFFF,
+ pvi->dwProductVersionLS >> 16, pvi->dwProductVersionLS & 0xFFFF);
+ }
+ }
+ }
+ }
+ if (!runtime_version_inspection_worked) {
+ LOG_INFO(Frontend, "Unable to inspect {}", runtime_dll_name);
+ }
+#endif
+}
+
+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();
+ u32 version = 10;
+ if (current_build >= 22000) {
+ version = 11;
+ }
+ 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();
+}
+
+bool GMainWindow::CheckDarkMode() {
+#ifdef __linux__
+ const QPalette test_palette(qApp->palette());
+ const QColor text_color = test_palette.color(QPalette::Active, QPalette::Text);
+ const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
+ return (text_color.value() > window_color.value());
+#else
+ // TODO: Windows
+ return false;
+#endif // __linux__
+}
+
+GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()},
- input_subsystem{std::make_shared<InputCommon::InputSubsystem>()},
- config{std::make_unique<Config>(*system)},
+ input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)},
vfs{std::make_shared<FileSys::RealVfsFilesystem>()},
provider{std::make_unique<FileSys::ManualContentProvider>()} {
+#ifdef __linux__
+ SetupSigInterrupts();
+#endif
+
Common::Log::Initialize();
LoadTranslation();
@@ -214,12 +290,21 @@ GMainWindow::GMainWindow()
ui->setupUi(this);
statusBar()->hide();
+ // Check dark mode before a theme is loaded
+ os_dark_mode = CheckDarkMode();
+ startup_icon_theme = QIcon::themeName();
+ // fallback can only be set once, colorful theme icons are okay on both light/dark
+ QIcon::setFallbackThemeName(QStringLiteral("colorful"));
+ QIcon::setFallbackSearchPaths(QStringList(QStringLiteral(":/icons")));
+
default_theme_paths = QIcon::themeSearchPaths();
UpdateUITheme();
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
discord_rpc->Update();
+ system->GetRoomNetwork().Init();
+
RegisterMetaTypes();
InitializeWidgets();
@@ -246,12 +331,13 @@ GMainWindow::GMainWindow()
const auto yuzu_build_version = override_build.empty() ? yuzu_build : override_build;
LOG_INFO(Frontend, "yuzu Version: {}", yuzu_build_version);
+ LogRuntimes();
#ifdef ARCHITECTURE_x86_64
const auto& caps = Common::GetCPUCaps();
std::string cpu_string = caps.cpu_string;
- if (caps.avx || caps.avx2 || caps.avx512) {
+ if (caps.avx || caps.avx2 || caps.avx512f) {
cpu_string += " | AVX";
- if (caps.avx512) {
+ if (caps.avx512f) {
cpu_string += "512";
} else if (caps.avx2) {
cpu_string += '2';
@@ -262,7 +348,7 @@ GMainWindow::GMainWindow()
}
LOG_INFO(Frontend, "Host CPU: {}", cpu_string);
#endif
- LOG_INFO(Frontend, "Host OS: {}", QSysInfo::prettyProductName().toStdString());
+ LOG_INFO(Frontend, "Host OS: {}", PrettyProductName().toStdString());
LOG_INFO(Frontend, "Host RAM: {:.2f} GiB",
Common::GetMemInfo().TotalPhysicalMemory / f64{1_GiB});
LOG_INFO(Frontend, "Host Swap: {:.2f} GiB", Common::GetMemInfo().TotalSwapMemory / f64{1_GiB});
@@ -291,13 +377,29 @@ GMainWindow::GMainWindow()
ui->menubar->setCursor(QCursor());
statusBar()->setCursor(QCursor());
- mouse_hide_timer.setInterval(default_mouse_timeout);
+ mouse_hide_timer.setInterval(default_mouse_hide_timeout);
connect(&mouse_hide_timer, &QTimer::timeout, this, &GMainWindow::HideMouseCursor);
connect(ui->menubar, &QMenuBar::hovered, this, &GMainWindow::ShowMouseCursor);
+ mouse_center_timer.setInterval(default_mouse_center_timeout);
+ connect(&mouse_center_timer, &QTimer::timeout, this, &GMainWindow::CenterMouseCursor);
+
MigrateConfigFiles();
- ui->action_Fullscreen->setChecked(false);
+ if (has_broken_vulkan) {
+ UISettings::values.has_broken_vulkan = true;
+
+ QMessageBox::warning(this, tr("Broken Vulkan Installation Detected"),
+ tr("Vulkan initialization failed during boot.<br><br>Click <a "
+ "href='https://yuzu-emu.org/wiki/faq/"
+ "#yuzu-starts-with-the-error-broken-vulkan-installation-detected'>"
+ "here for instructions to fix the issue</a>."));
+
+ Settings::values.renderer_backend = Settings::RendererBackend::OpenGL;
+
+ renderer_status_button->setDisabled(true);
+ renderer_status_button->setChecked(false);
+ }
#if defined(HAVE_SDL2) && !defined(_WIN32)
SDL_InitSubSystem(SDL_INIT_VIDEO);
@@ -307,6 +409,8 @@ GMainWindow::GMainWindow()
SDL_EnableScreenSaver();
#endif
+ SetupPrepareForSleep();
+
Common::Log::Start();
QStringList args = QApplication::arguments();
@@ -316,17 +420,20 @@ GMainWindow::GMainWindow()
}
QString game_path;
+ bool has_gamepath = false;
+ bool is_fullscreen = false;
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];
+ has_gamepath = true;
break;
}
// Launch game in fullscreen mode
if (args[i] == QStringLiteral("-f")) {
- ui->action_Fullscreen->setChecked(true);
+ is_fullscreen = true;
continue;
}
@@ -369,9 +476,15 @@ GMainWindow::GMainWindow()
}
game_path = args[++i];
+ has_gamepath = true;
}
}
+ // Override fullscreen setting if gamepath or argument is provided
+ if (has_gamepath || is_fullscreen) {
+ ui->action_Fullscreen->setChecked(is_fullscreen);
+ }
+
if (!game_path.isEmpty()) {
BootGame(game_path);
}
@@ -382,6 +495,11 @@ GMainWindow::~GMainWindow() {
if (render_window->parent() == nullptr) {
delete render_window;
}
+
+#ifdef __linux__
+ ::close(sig_interrupt_fds[0]);
+ ::close(sig_interrupt_fds[1]);
+#endif
}
void GMainWindow::RegisterMetaTypes() {
@@ -586,7 +704,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url,
#ifdef YUZU_USE_QT_WEB_ENGINE
// Raw input breaks with the web applet, Disable web applets if enabled
- if (disable_web_applet || Settings::values.enable_raw_input) {
+ if (UISettings::values.disable_web_applet || Settings::values.enable_raw_input) {
emit WebBrowserClosed(Service::AM::Applets::WebExitReason::WindowClosed,
"http://localhost/");
return;
@@ -651,12 +769,12 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url,
connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] {
const auto result = QMessageBox::warning(
this, tr("Disable Web Applet"),
- tr("Disabling the web applet will cause it to not be shown again for the rest of the "
- "emulated session. This can lead to undefined behavior and should only be used with "
- "Super Mario 3D All-Stars. Are you sure you want to disable the web applet?"),
+ tr("Disabling the web applet can lead to undefined behavior and should only be used "
+ "with Super Mario 3D All-Stars. Are you sure you want to disable the web "
+ "applet?\n(This can be re-enabled in the Debug settings.)"),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::Yes) {
- disable_web_applet = true;
+ UISettings::values.disable_web_applet = true;
web_browser_view.SetFinished(true);
}
});
@@ -745,6 +863,10 @@ void GMainWindow::InitializeWidgets() {
}
});
+ multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
+ ui->action_Show_Room, *system);
+ multiplayer_state->setVisible(false);
+
// Create status bar
message_label = new QLabel();
// Configured separately for left alignment
@@ -777,6 +899,10 @@ void GMainWindow::InitializeWidgets() {
statusBar()->addPermanentWidget(label);
}
+ // TODO (flTobi): Add the widget when multiplayer is fully implemented
+ statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
+ statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
+
tas_label = new QLabel();
tas_label->setObjectName(QStringLiteral("TASlabel"));
tas_label->setFocusPolicy(Qt::NoFocus);
@@ -820,12 +946,11 @@ void GMainWindow::InitializeWidgets() {
// Setup Dock button
dock_status_button = new QPushButton();
- dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
+ dock_status_button->setObjectName(QStringLiteral("DockingStatusBarButton"));
dock_status_button->setFocusPolicy(Qt::NoFocus);
connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode);
- dock_status_button->setText(tr("DOCK"));
dock_status_button->setCheckable(true);
- dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
+ UpdateDockedButton();
statusBar()->insertPermanentWidget(0, dock_status_button);
gpu_accuracy_button = new QPushButton();
@@ -856,8 +981,7 @@ void GMainWindow::InitializeWidgets() {
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Vulkan);
} else {
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::OpenGL);
- const auto filter = Settings::values.scaling_filter.GetValue();
- if (filter == Settings::ScalingFilter::Fsr) {
+ if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) {
Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor);
UpdateFilterText();
}
@@ -926,15 +1050,16 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name
auto* controller = system->HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
const auto* controller_hotkey =
hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
- connect(controller_hotkey, &ControllerShortcut::Activated, this,
- [action] { action->trigger(); });
+ connect(
+ controller_hotkey, &ControllerShortcut::Activated, this, [action] { action->trigger(); },
+ Qt::QueuedConnection);
}
void GMainWindow::InitializeHotkeys() {
hotkey_registry.LoadHotkeys();
LinkActionShortcut(ui->action_Load_File, QStringLiteral("Load File"));
- LinkActionShortcut(ui->action_Load_Amiibo, QStringLiteral("Load Amiibo"));
+ LinkActionShortcut(ui->action_Load_Amiibo, QStringLiteral("Load/Remove Amiibo"));
LinkActionShortcut(ui->action_Exit, QStringLiteral("Exit yuzu"));
LinkActionShortcut(ui->action_Restart, QStringLiteral("Restart Emulation"));
LinkActionShortcut(ui->action_Pause, QStringLiteral("Continue/Pause Emulation"));
@@ -954,7 +1079,8 @@ void GMainWindow::InitializeHotkeys() {
const auto* controller_hotkey =
hotkey_registry.GetControllerHotkey(main_window, action_name, controller);
connect(hotkey, &QShortcut::activated, this, function);
- connect(controller_hotkey, &ControllerShortcut::Activated, this, function);
+ connect(controller_hotkey, &ControllerShortcut::Activated, this, function,
+ Qt::QueuedConnection);
};
connect_shortcut(QStringLiteral("Exit Fullscreen"), [&] {
@@ -970,17 +1096,29 @@ void GMainWindow::InitializeHotkeys() {
connect_shortcut(QStringLiteral("Audio Mute/Unmute"),
[] { Settings::values.audio_muted = !Settings::values.audio_muted; });
connect_shortcut(QStringLiteral("Audio Volume Down"), [] {
- const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
- const auto new_volume = std::max(current_volume - 5, 0);
- Settings::values.volume.SetValue(static_cast<u8>(new_volume));
+ const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue());
+ int step = 5;
+ if (current_volume <= 30) {
+ step = 2;
+ }
+ if (current_volume <= 6) {
+ step = 1;
+ }
+ Settings::values.volume.SetValue(std::max(current_volume - step, 0));
});
connect_shortcut(QStringLiteral("Audio Volume Up"), [] {
- const auto current_volume = static_cast<int>(Settings::values.volume.GetValue());
- const auto new_volume = std::min(current_volume + 5, 100);
- Settings::values.volume.SetValue(static_cast<u8>(new_volume));
+ const auto current_volume = static_cast<s32>(Settings::values.volume.GetValue());
+ int step = 5;
+ if (current_volume < 30) {
+ step = 2;
+ }
+ if (current_volume < 6) {
+ step = 1;
+ }
+ Settings::values.volume.SetValue(current_volume + step);
});
connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] {
- Settings::values.disable_fps_limit.SetValue(!Settings::values.disable_fps_limit.GetValue());
+ Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
});
connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
Settings::values.mouse_panning = !Settings::values.mouse_panning;
@@ -993,7 +1131,7 @@ void GMainWindow::InitializeHotkeys() {
void GMainWindow::SetDefaultUIGeometry() {
// geometry: 53% of the window contents are in the upper screen half, 47% in the lower half
- const QRect screenRect = QApplication::desktop()->screenGeometry(this);
+ const QRect screenRect = QGuiApplication::primaryScreen()->geometry();
const int w = screenRect.width() * 2 / 3;
const int h = screenRect.height() * 2 / 3;
@@ -1006,6 +1144,10 @@ void GMainWindow::SetDefaultUIGeometry() {
void GMainWindow::RestoreUIState() {
setWindowFlags(windowFlags() & ~Qt::FramelessWindowHint);
restoreGeometry(UISettings::values.geometry);
+ // Work-around because the games list isn't supposed to be full screen
+ if (isFullScreen()) {
+ showNormal();
+ }
restoreState(UISettings::values.state);
render_window->setWindowFlags(render_window->windowFlags() & ~Qt::FramelessWindowHint);
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
@@ -1048,6 +1190,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
OnPauseGame();
} else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) {
auto_paused = false;
+ RequestGameResume();
OnStartGame();
}
}
@@ -1081,6 +1224,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
&GMainWindow::OnGameListAddDirectory);
connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
+ connect(game_list, &GameList::PopulatingCompleted,
+ [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); });
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
&GMainWindow::OnGameListOpenPerGameProperties);
@@ -1098,6 +1243,9 @@ void GMainWindow::ConnectWidgetEvents() {
connect(this, &GMainWindow::EmulationStopping, this, &GMainWindow::SoftwareKeyboardExit);
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
+
+ connect(this, &GMainWindow::UpdateThemedIcons, multiplayer_state,
+ &MultiplayerState::UpdateThemedIcons);
}
void GMainWindow::ConnectMenuEvents() {
@@ -1141,6 +1289,19 @@ void GMainWindow::ConnectMenuEvents() {
ui->action_Reset_Window_Size_900,
ui->action_Reset_Window_Size_1080});
+ // 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);
+ connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig);
+
// Tools
connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
ReinitializeKeyBehavior::Warning));
@@ -1180,6 +1341,8 @@ void GMainWindow::UpdateMenuState() {
} else {
ui->action_Pause->setText(tr("&Pause"));
}
+
+ multiplayer_state->UpdateNotificationStatus();
}
void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -1202,6 +1365,43 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
}
}
+void GMainWindow::SetupPrepareForSleep() {
+#ifdef __linux__
+ auto bus = QDBusConnection::systemBus();
+ if (bus.isConnected()) {
+ const bool success = bus.connect(
+ QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"),
+ QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("PrepareForSleep"),
+ QStringLiteral("b"), this, SLOT(OnPrepareForSleep(bool)));
+
+ if (!success) {
+ LOG_WARNING(Frontend, "Couldn't register PrepareForSleep signal");
+ }
+ } else {
+ LOG_WARNING(Frontend, "QDBusConnection system bus is not connected");
+ }
+#endif // __linux__
+}
+
+void GMainWindow::OnPrepareForSleep(bool prepare_sleep) {
+ if (emu_thread == nullptr) {
+ return;
+ }
+
+ if (prepare_sleep) {
+ if (emu_thread->IsRunning()) {
+ auto_paused = true;
+ OnPauseGame();
+ }
+ } else {
+ if (!emu_thread->IsRunning() && auto_paused) {
+ auto_paused = false;
+ RequestGameResume();
+ OnStartGame();
+ }
+ }
+}
+
#ifdef __linux__
static std::optional<QDBusObjectPath> HoldWakeLockLinux(u32 window_id = 0) {
if (!QDBusConnection::sessionBus().isConnected()) {
@@ -1241,6 +1441,52 @@ static void ReleaseWakeLockLinux(QDBusObjectPath lock) {
QString::fromLatin1("org.freedesktop.portal.Request"));
unlocker.call(QString::fromLatin1("Close"));
}
+
+std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0};
+
+void GMainWindow::SetupSigInterrupts() {
+ if (sig_interrupt_fds[2] == 1) {
+ return;
+ }
+ socketpair(AF_UNIX, SOCK_STREAM, 0, sig_interrupt_fds.data());
+ sig_interrupt_fds[2] = 1;
+
+ struct sigaction sa;
+ sa.sa_handler = &GMainWindow::HandleSigInterrupt;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESETHAND;
+ sigaction(SIGINT, &sa, nullptr);
+ sigaction(SIGTERM, &sa, nullptr);
+
+ sig_interrupt_notifier = new QSocketNotifier(sig_interrupt_fds[1], QSocketNotifier::Read, this);
+ connect(sig_interrupt_notifier, &QSocketNotifier::activated, this,
+ &GMainWindow::OnSigInterruptNotifierActivated);
+ connect(this, &GMainWindow::SigInterrupt, this, &GMainWindow::close);
+}
+
+void GMainWindow::HandleSigInterrupt(int sig) {
+ if (sig == SIGINT) {
+ exit(1);
+ }
+
+ // Calling into Qt directly from a signal handler is not safe,
+ // so wake up a QSocketNotifier with this hacky write call instead.
+ char a = 1;
+ int ret = write(sig_interrupt_fds[0], &a, sizeof(a));
+ (void)ret;
+}
+
+void GMainWindow::OnSigInterruptNotifierActivated() {
+ sig_interrupt_notifier->setEnabled(false);
+
+ char a;
+ int ret = read(sig_interrupt_fds[1], &a, sizeof(a));
+ (void)ret;
+
+ sig_interrupt_notifier->setEnabled(true);
+
+ emit SigInterrupt();
+}
#endif // __linux__
void GMainWindow::PreventOSSleep() {
@@ -1284,6 +1530,7 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
system->SetAppletFrontendSet({
std::make_unique<QtControllerSelector>(*this), // Controller Selector
std::make_unique<QtErrorDisplay>(*this), // Error Display
+ nullptr, // Mii Editor
nullptr, // Parental Controls
nullptr, // Photo Viewer
std::make_unique<QtProfileSelector>(*this), // Profile Selector
@@ -1357,23 +1604,24 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p
}
return false;
}
- game_path = filename;
+ current_game_path = filename;
system->TelemetrySession().AddField(Common::Telemetry::FieldType::App, "Frontend", "Qt");
return true;
}
-void GMainWindow::SelectAndSetCurrentUser() {
+bool GMainWindow::SelectAndSetCurrentUser() {
QtProfileSelectionDialog dialog(system->HIDCore(), this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Rejected) {
- return;
+ return false;
}
Settings::values.current_user = dialog.GetIndex();
+ return true;
}
void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t program_index,
@@ -1391,16 +1639,15 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success &&
type == StartGameType::Normal) {
// Load per game settings
- const auto file_path = std::filesystem::path{filename.toStdU16String()};
+ const auto file_path =
+ std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())};
const auto config_file_name = title_id == 0
? Common::FS::PathToUTF8String(file_path.filename())
: fmt::format("{:016X}", title_id);
- Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig);
+ Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
+ system->ApplySettings();
}
- // Disable fps limit toggle when booting a new title
- Settings::values.disable_fps_limit.SetValue(false);
-
// Save configurations
UpdateUISettings();
game_list->SaveInterfaceLayout();
@@ -1409,11 +1656,16 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
Settings::LogSettings();
if (UISettings::values.select_user_on_boot) {
- SelectAndSetCurrentUser();
+ if (SelectAndSetCurrentUser() == false) {
+ return;
+ }
}
- if (!LoadROM(filename, program_id, program_index))
+ if (!LoadROM(filename, program_id, program_index)) {
return;
+ }
+
+ system->SetShuttingDown(false);
// Create and start the emulation thread
emu_thread = std::make_unique<EmuThread>(*system);
@@ -1422,7 +1674,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
// Register an ExecuteProgram callback such that Core can execute a sub-program
system->RegisterExecuteProgramCallback(
- [this](std::size_t program_index) { render_window->ExecuteProgram(program_index); });
+ [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); });
// Register an Exit callback such that Core can exit the currently running application.
system->RegisterExitCallback([this]() { render_window->Exit(); });
@@ -1457,6 +1709,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
mouse_hide_timer.start();
}
+ render_window->InitializeCamera();
+
std::string title_name;
std::string title_version;
const auto res = system->GetGameName(title_name);
@@ -1472,7 +1726,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
}
if (res != Loader::ResultStatus::Success || title_name.empty()) {
title_name = Common::FS::PathToUTF8String(
- std::filesystem::path{filename.toStdU16String()}.filename());
+ std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())}
+ .filename());
}
const bool is_64bit = system->Kernel().CurrentProcess()->Is64BitProcess();
const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)");
@@ -1504,6 +1759,8 @@ void GMainWindow::ShutdownGame() {
AllowOSSleep();
+ system->SetShuttingDown(true);
+ system->DetachDebugger();
discord_rpc->Pause();
emu_thread->RequestStop();
@@ -1535,6 +1792,7 @@ void GMainWindow::ShutdownGame() {
tas_label->clear();
input_subsystem->GetTas()->Stop();
OnTasStateChanged();
+ render_window->FinalizeCamera();
// Enable all controllers
system->HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
@@ -1551,9 +1809,9 @@ void GMainWindow::ShutdownGame() {
emu_speed_label->setVisible(false);
game_fps_label->setVisible(false);
emu_frametime_label->setVisible(false);
- renderer_status_button->setEnabled(true);
+ renderer_status_button->setEnabled(!UISettings::values.has_broken_vulkan);
- game_path.clear();
+ current_game_path.clear();
// When closing the game, destroy the GLWindow to clear the context after the game is closed
render_window->ReleaseRenderTarget();
@@ -1571,7 +1829,7 @@ void GMainWindow::StoreRecentFile(const QString& filename) {
void GMainWindow::UpdateRecentFiles() {
const int num_recent_files =
- std::min(UISettings::values.recent_files.size(), max_recent_files_item);
+ 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(
@@ -1746,7 +2004,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
- const QString entry_type = [this, type] {
+ const QString entry_type = [type] {
switch (type) {
case InstalledEntryType::Game:
return tr("Contents");
@@ -1843,7 +2101,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
const std::string& game_path) {
- const QString question = [this, target] {
+ const QString question = [target] {
switch (target) {
case GameListRemoveTarget::GlShaderCache:
return tr("Delete OpenGL Transferable Shader Cache?");
@@ -2472,7 +2730,7 @@ void GMainWindow::OnRestartGame() {
return;
}
// Make a copy since BootGame edits game_path
- BootGame(QString(game_path));
+ BootGame(QString(current_game_path));
}
void GMainWindow::OnPauseGame() {
@@ -2486,6 +2744,7 @@ void GMainWindow::OnPauseContinueGame() {
if (emu_thread->IsRunning()) {
OnPauseGame();
} else {
+ RequestGameResume();
OnStartGame();
}
}
@@ -2515,6 +2774,11 @@ void GMainWindow::OnExit() {
OnStopGame();
}
+void GMainWindow::OnSaveConfig() {
+ system->ApplySettings();
+ config->Save();
+}
+
void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"),
Qt::AlignLeft | Qt::AlignVCenter);
@@ -2569,6 +2833,18 @@ void GMainWindow::ToggleFullscreen() {
}
}
+// We're going to return the screen that the given window has the most pixels on
+static QScreen* GuessCurrentScreen(QWidget* window) {
+ const QList<QScreen*> screens = QGuiApplication::screens();
+ return *std::max_element(
+ screens.cbegin(), screens.cend(), [window](const QScreen* left, const QScreen* right) {
+ const QSize left_size = left->geometry().intersected(window->geometry()).size();
+ const QSize right_size = right->geometry().intersected(window->geometry()).size();
+ return (left_size.height() * left_size.width()) <
+ (right_size.height() * right_size.width());
+ });
+}
+
void GMainWindow::ShowFullscreen() {
const auto show_fullscreen = [](QWidget* window) {
if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
@@ -2577,7 +2853,7 @@ void GMainWindow::ShowFullscreen() {
}
window->hide();
window->setWindowFlags(window->windowFlags() | Qt::FramelessWindowHint);
- const auto screen_geometry = QApplication::desktop()->screenGeometry(window);
+ const auto screen_geometry = GuessCurrentScreen(window)->geometry();
window->setGeometry(screen_geometry.x(), screen_geometry.y(), screen_geometry.width(),
screen_geometry.height() + 1);
window->raise();
@@ -2681,7 +2957,8 @@ void GMainWindow::OnConfigure() {
const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue();
Settings::SetConfiguringGlobal(true);
- ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system);
+ ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system,
+ !multiplayer_state->IsHostingPublicRoom());
connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this,
&GMainWindow::OnLanguageChanged);
@@ -2717,7 +2994,7 @@ void GMainWindow::OnConfigure() {
Settings::values.disabled_addons.clear();
- config = std::make_unique<Config>(*system);
+ config = std::make_unique<Config>();
UISettings::values.reset_to_defaults = false;
UISettings::values.game_dirs = std::move(old_game_dirs);
@@ -2738,6 +3015,11 @@ void GMainWindow::OnConfigure() {
if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) {
SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue());
}
+
+ if (!multiplayer_state->IsHostingPublicRoom()) {
+ multiplayer_state->UpdateCredentials();
+ }
+
emit UpdateThemedIcons();
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
@@ -2761,8 +3043,19 @@ void GMainWindow::OnConfigure() {
mouse_hide_timer.start();
}
+ // Restart camera config
+ if (emulation_running) {
+ render_window->FinalizeCamera();
+ render_window->InitializeCamera();
+ }
+
+ if (!UISettings::values.has_broken_vulkan) {
+ renderer_status_button->setEnabled(!emulation_running);
+ }
+
UpdateStatusButtons();
controller_dialog->refreshConfiguration();
+ system->ApplySettings();
}
void GMainWindow::OnConfigureTas() {
@@ -2846,7 +3139,7 @@ void GMainWindow::OnToggleDockedMode() {
}
Settings::values.use_docked_mode.SetValue(!is_docked);
- dock_status_button->setChecked(!is_docked);
+ UpdateDockedButton();
OnDockedModeChanged(is_docked, !is_docked, *system);
}
@@ -2885,7 +3178,7 @@ void GMainWindow::OnToggleAdaptingFilter() {
void GMainWindow::OnConfigurePerGame() {
const u64 title_id = system->GetCurrentProcessProgramID();
- OpenPerGameConfiguration(title_id, game_path.toStdString());
+ OpenPerGameConfiguration(title_id, current_game_path.toStdString());
}
void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file_name) {
@@ -2927,6 +3220,20 @@ void GMainWindow::OnLoadAmiibo() {
return;
}
+ auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
+
+ // Remove amiibo if one is connected
+ if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+ virtual_amiibo->CloseAmiibo();
+ QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
+ return;
+ }
+
+ if (virtual_amiibo->GetCurrentState() != InputCommon::VirtualAmiibo::State::WaitingForAmiibo) {
+ QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
+ return;
+ }
+
is_amiibo_file_select_active = true;
const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
@@ -2941,34 +3248,30 @@ void GMainWindow::OnLoadAmiibo() {
}
void GMainWindow::LoadAmiibo(const QString& filename) {
- Service::SM::ServiceManager& sm = system->ServiceManager();
- auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
- if (nfc == nullptr) {
+ auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
+ const QString title = tr("Error loading Amiibo data");
+ // Remove amiibo if one is connected
+ if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+ virtual_amiibo->CloseAmiibo();
+ QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
return;
}
- QFile nfc_file{filename};
- if (!nfc_file.open(QIODevice::ReadOnly)) {
- QMessageBox::warning(this, tr("Error opening Amiibo data file"),
- tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
- return;
- }
-
- const u64 nfc_file_size = nfc_file.size();
- std::vector<u8> buffer(nfc_file_size);
- const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
- if (nfc_file_size != read_size) {
- QMessageBox::warning(this, tr("Error reading Amiibo data file"),
- tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
- "was only able to read %2 bytes.")
- .arg(nfc_file_size)
- .arg(read_size));
- return;
- }
-
- if (!nfc->LoadAmiibo(buffer)) {
- QMessageBox::warning(this, tr("Error loading Amiibo data"),
- tr("Unable to load Amiibo data."));
+ switch (virtual_amiibo->LoadAmiibo(filename.toStdString())) {
+ case InputCommon::VirtualAmiibo::Info::NotAnAmiibo:
+ QMessageBox::warning(this, title, tr("The selected file is not a valid amiibo"));
+ break;
+ case InputCommon::VirtualAmiibo::Info::UnableToLoad:
+ QMessageBox::warning(this, title, tr("The selected file is already on use"));
+ break;
+ case InputCommon::VirtualAmiibo::Info::WrongDeviceState:
+ QMessageBox::warning(this, title, tr("The current game is not looking for amiibos"));
+ break;
+ case InputCommon::VirtualAmiibo::Info::Unknown:
+ QMessageBox::warning(this, title, tr("An unkown error occured"));
+ break;
+ default:
+ break;
}
}
@@ -3046,7 +3349,8 @@ void GMainWindow::MigrateConfigFiles() {
}
const auto origin = config_dir_fs_path / filename;
const auto destination = config_dir_fs_path / "custom" / filename;
- LOG_INFO(Frontend, "Migrating config file from {} to {}", origin, destination);
+ LOG_INFO(Frontend, "Migrating config file from {} to {}", origin.string(),
+ destination.string());
if (!Common::FS::RenameFile(origin, destination)) {
// Delete the old config file if one already exists in the new location.
Common::FS::RemoveFile(origin);
@@ -3112,7 +3416,7 @@ void GMainWindow::OnTasStateChanged() {
}
void GMainWindow::UpdateStatusBar() {
- if (emu_thread == nullptr) {
+ if (emu_thread == nullptr || !system->IsPoweredOn()) {
status_bar_update_timer.stop();
return;
}
@@ -3146,11 +3450,12 @@ void GMainWindow::UpdateStatusBar() {
} else {
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
}
- if (Settings::values.disable_fps_limit) {
+ if (!Settings::values.use_speed_limit) {
game_fps_label->setText(
- tr("Game: %1 FPS (Unlocked)").arg(results.average_game_fps, 0, 'f', 0));
+ tr("Game: %1 FPS (Unlocked)").arg(std::round(results.average_game_fps), 0, 'f', 0));
} else {
- game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0));
+ game_fps_label->setText(
+ tr("Game: %1 FPS").arg(std::round(results.average_game_fps), 0, 'f', 0));
}
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
@@ -3184,6 +3489,12 @@ void GMainWindow::UpdateGPUAccuracyButton() {
}
}
+void GMainWindow::UpdateDockedButton() {
+ const bool is_docked = Settings::values.use_docked_mode.GetValue();
+ dock_status_button->setChecked(is_docked);
+ dock_status_button->setText(is_docked ? tr("DOCKED") : tr("HANDHELD"));
+}
+
void GMainWindow::UpdateFilterText() {
const auto filter = Settings::values.scaling_filter.GetValue();
switch (filter) {
@@ -3227,10 +3538,10 @@ void GMainWindow::UpdateAAText() {
}
void GMainWindow::UpdateStatusButtons() {
- dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan);
UpdateGPUAccuracyButton();
+ UpdateDockedButton();
UpdateFilterText();
UpdateAAText();
}
@@ -3269,10 +3580,26 @@ void GMainWindow::ShowMouseCursor() {
}
}
+void GMainWindow::CenterMouseCursor() {
+ if (emu_thread == nullptr || !Settings::values.mouse_panning) {
+ mouse_center_timer.stop();
+ return;
+ }
+ if (!this->isActiveWindow()) {
+ mouse_center_timer.stop();
+ return;
+ }
+ const int center_x = render_window->width() / 2;
+ const int center_y = render_window->height() / 2;
+
+ QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
+}
+
void GMainWindow::OnMouseActivity() {
if (!Settings::values.mouse_panning) {
ShowMouseCursor();
}
+ mouse_center_timer.stop();
}
void GMainWindow::OnCoreError(Core::SystemResultStatus result, std::string details) {
@@ -3498,6 +3825,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
}
render_window->close();
+ multiplayer_state->Close();
+ system->GetRoomNetwork().Shutdown();
QWidget::closeEvent(event);
}
@@ -3545,6 +3874,22 @@ void GMainWindow::dragMoveEvent(QDragMoveEvent* event) {
AcceptDropEvent(event);
}
+void GMainWindow::leaveEvent(QEvent* event) {
+ if (Settings::values.mouse_panning) {
+ const QRect& rect = geometry();
+ QPoint position = QCursor::pos();
+
+ qint32 x = qBound(rect.left(), position.x(), rect.right());
+ qint32 y = qBound(rect.top(), position.y(), rect.bottom());
+ // Only start the timer if the mouse has left the window bound.
+ // The leave event is also triggered when the window looses focus.
+ if (x != position.x() || y != position.y()) {
+ mouse_center_timer.start();
+ }
+ event->accept();
+ }
+}
+
bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
@@ -3583,13 +3928,41 @@ void GMainWindow::RequestGameExit() {
}
}
+void GMainWindow::RequestGameResume() {
+ auto& sm{system->ServiceManager()};
+ auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
+ auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
+
+ if (applet_oe != nullptr) {
+ applet_oe->GetMessageQueue()->RequestResume();
+ return;
+ }
+
+ if (applet_ae != nullptr) {
+ applet_ae->GetMessageQueue()->RequestResume();
+ }
+}
+
void GMainWindow::filterBarSetChecked(bool state) {
ui->action_Show_Filter_Bar->setChecked(state);
emit(OnToggleFilterBar());
}
+static void AdjustLinkColor() {
+ QPalette new_pal(qApp->palette());
+ if (UISettings::IsDarkTheme()) {
+ new_pal.setColor(QPalette::Link, QColor(0, 190, 255, 255));
+ } else {
+ new_pal.setColor(QPalette::Link, QColor(0, 140, 200, 255));
+ }
+ if (qApp->palette().color(QPalette::Link) != new_pal.color(QPalette::Link)) {
+ qApp->setPalette(new_pal);
+ }
+}
+
void GMainWindow::UpdateUITheme() {
- const QString default_theme = QStringLiteral("default");
+ const QString default_theme =
+ QString::fromUtf8(UISettings::themes[static_cast<size_t>(Config::default_theme)].second);
QString current_theme = UISettings::values.theme;
QStringList theme_paths(default_theme_paths);
@@ -3597,6 +3970,23 @@ void GMainWindow::UpdateUITheme() {
current_theme = default_theme;
}
+#ifdef _WIN32
+ QIcon::setThemeName(current_theme);
+ AdjustLinkColor();
+#else
+ if (current_theme == QStringLiteral("default") || current_theme == QStringLiteral("colorful")) {
+ QIcon::setThemeName(current_theme == QStringLiteral("colorful") ? current_theme
+ : startup_icon_theme);
+ QIcon::setThemeSearchPaths(theme_paths);
+ if (CheckDarkMode()) {
+ current_theme = QStringLiteral("default_dark");
+ }
+ } else {
+ QIcon::setThemeName(current_theme);
+ QIcon::setThemeSearchPaths(QStringList(QStringLiteral(":/icons")));
+ AdjustLinkColor();
+ }
+#endif
if (current_theme != default_theme) {
QString theme_uri{QStringLiteral(":%1/style.qss").arg(current_theme)};
QFile f(theme_uri);
@@ -3619,17 +4009,9 @@ void GMainWindow::UpdateUITheme() {
qApp->setStyleSheet({});
setStyleSheet({});
}
-
- 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()) {
@@ -3655,6 +4037,7 @@ void GMainWindow::OnLanguageChanged(const QString& locale) {
UISettings::values.language = locale;
LoadTranslation();
ui->retranslateUi(this);
+ multiplayer_state->retranslateUi();
UpdateWindowTitle();
}
@@ -3671,11 +4054,54 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
discord_rpc->Update();
}
+void GMainWindow::changeEvent(QEvent* event) {
+#ifdef __linux__
+ // PaletteChange event appears to only reach so far into the GUI, explicitly asking to
+ // UpdateUITheme is a decent work around
+ if (event->type() == QEvent::PaletteChange) {
+ const QPalette test_palette(qApp->palette());
+ const QString current_theme = UISettings::values.theme;
+ // Keeping eye on QPalette::Window to avoid looping. QPalette::Text might be useful too
+ static QColor last_window_color;
+ const QColor window_color = test_palette.color(QPalette::Active, QPalette::Window);
+ if (last_window_color != window_color && (current_theme == QStringLiteral("default") ||
+ current_theme == QStringLiteral("colorful"))) {
+ UpdateUITheme();
+ }
+ last_window_color = window_color;
+ }
+#endif // __linux__
+ QWidget::changeEvent(event);
+}
+
#ifdef main
#undef main
#endif
int main(int argc, char* argv[]) {
+ std::unique_ptr<Config> config = std::make_unique<Config>();
+ bool has_broken_vulkan = false;
+ bool is_child = false;
+ if (CheckEnvVars(&is_child)) {
+ return 0;
+ }
+
+#ifdef YUZU_DBGHELP
+ PROCESS_INFORMATION pi;
+ if (!is_child && Settings::values.create_crash_dumps.GetValue() &&
+ MiniDump::SpawnDebuggee(argv[0], pi)) {
+ // Delete the config object so that it doesn't save when the program exits
+ config.reset(nullptr);
+ MiniDump::DebugDebuggee(pi);
+ return 0;
+ }
+#endif
+
+ if (StartupChecks(argv[0], &has_broken_vulkan,
+ Settings::values.perform_vulkan_check.GetValue())) {
+ return 0;
+ }
+
Common::DetachedTasks detached_tasks;
MicroProfileOnThreadCreate("Frontend");
SCOPE_EXIT({ MicroProfileShutdown(); });
@@ -3711,11 +4137,20 @@ int main(int argc, char* argv[]) {
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
QApplication app(argc, argv);
+ // Workaround for QTBUG-85409, for Suzhou numerals the number 1 is actually \u3021
+ // so we can see if we get \u3008 instead
+ // TL;DR all other number formats are consecutive in unicode code points
+ // This bug is fixed in Qt6, specifically 6.0.0-alpha1
+ const QLocale locale = QLocale::system();
+ if (QStringLiteral("\u3008") == locale.toString(1)) {
+ QLocale::setDefault(QLocale::system().name());
+ }
+
// Qt changes the locale and causes issues in float conversion using std::to_string() when
// generating shaders
setlocale(LC_ALL, "C");
- GMainWindow main_window{};
+ GMainWindow main_window{std::move(config), has_broken_vulkan};
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();