diff options
Diffstat (limited to 'src/yuzu/main.cpp')
-rw-r--r-- | src/yuzu/main.cpp | 744 |
1 files changed, 512 insertions, 232 deletions
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e57e02652..24e59f646 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -27,6 +27,7 @@ #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" #include "configuration/configure_tas.h" +#include "core/file_sys/romfs_factory.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/cabinet.h" @@ -91,6 +92,9 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "common/microprofile.h" #include "common/scm_rev.h" #include "common/scope_exit.h" +#ifdef _WIN32 +#include "common/windows/timer_resolution.h" +#endif #ifdef ARCHITECTURE_x86_64 #include "common/x64/cpu_detect.h" #endif @@ -143,6 +147,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/startup_checks.h" #include "yuzu/uisettings.h" #include "yuzu/util/clickable_label.h" +#include "yuzu/vk_device_info.h" #ifdef YUZU_DBGHELP #include "yuzu/mini_dump.h" @@ -219,7 +224,7 @@ 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"; + static constexpr char runtime_dll_name[] = "vcruntime140.dll"; UINT sz = GetFileVersionInfoSizeA(runtime_dll_name, nullptr); bool runtime_version_inspection_worked = false; if (sz > 0) { @@ -271,7 +276,7 @@ static QString PrettyProductName() { #ifdef _WIN32 static void OverrideWindowsFont() { - // Qt5 chooses these fonts on Windows and they have fairly ugly alphanumeric/cyrllic characters + // Qt5 chooses these fonts on Windows and they have fairly ugly alphanumeric/cyrillic characters // Asking to use "MS Shell Dlg 2" gives better other chars while leaving the Chinese Characters. const QString startup_font = QApplication::font().family(); const QStringList ugly_fonts = {QStringLiteral("SimSun"), QStringLiteral("PMingLiU")}; @@ -304,6 +309,8 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan system->Initialize(); Common::Log::Initialize(); + Common::Log::Start(); + LoadTranslation(); setAcceptDrops(true); @@ -377,6 +384,12 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan 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}); +#ifdef _WIN32 + LOG_INFO(Frontend, "Host Timer Resolution: {:.4f} ms", + std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>( + Common::Windows::SetCurrentTimerResolutionToMaximum()) + .count()); +#endif UpdateWindowTitle(); show(); @@ -428,10 +441,20 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan renderer_status_button->setDisabled(true); renderer_status_button->setChecked(false); + } else { + VkDeviceInfo::PopulateRecords(vk_device_records, this->window()->windowHandle()); } #if defined(HAVE_SDL2) && !defined(_WIN32) SDL_InitSubSystem(SDL_INIT_VIDEO); + + // Set a screensaver inhibition reason string. Currently passed to DBus by SDL and visible to + // the user through their desktop environment. + //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the + //: computer from sleeping + QByteArray wakelock_reason = tr("Running a game").toLatin1(); + SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data()); + // SDL disables the screen saver by default, and setting the hint // SDL_HINT_VIDEO_ALLOW_SCREENSAVER doesn't seem to work, so we just enable the screen saver // for now. @@ -440,8 +463,6 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan SetupPrepareForSleep(); - Common::Log::Start(); - QStringList args = QApplication::arguments(); if (args.size() < 2) { @@ -561,12 +582,16 @@ void GMainWindow::RegisterMetaTypes() { // Cabinet Applet qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters"); - qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>( - "std::shared_ptr<Service::NFP::NfpDevice>"); + qRegisterMetaType<std::shared_ptr<Service::NFC::NfcDevice>>( + "std::shared_ptr<Service::NFC::NfcDevice>"); // Controller Applet qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters"); + // Profile Select Applet + qRegisterMetaType<Core::Frontend::ProfileSelectParameters>( + "Core::Frontend::ProfileSelectParameters"); + // Software Keyboard Applet qRegisterMetaType<Core::Frontend::KeyboardInitializeParameters>( "Core::Frontend::KeyboardInitializeParameters"); @@ -586,51 +611,82 @@ void GMainWindow::RegisterMetaTypes() { } void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters, - std::shared_ptr<Service::NFP::NfpDevice> nfp_device) { - QtAmiiboSettingsDialog dialog(this, parameters, input_subsystem.get(), nfp_device); + std::shared_ptr<Service::NFC::NfcDevice> nfp_device) { + cabinet_applet = + new QtAmiiboSettingsDialog(this, parameters, input_subsystem.get(), nfp_device); + SCOPE_EXIT({ + cabinet_applet->deleteLater(); + cabinet_applet = nullptr; + }); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Rejected) { + cabinet_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | + Qt::WindowTitleHint | Qt::WindowSystemMenuHint); + cabinet_applet->setWindowModality(Qt::WindowModal); + + if (cabinet_applet->exec() == QDialog::Rejected) { emit AmiiboSettingsFinished(false, {}); return; } - emit AmiiboSettingsFinished(true, dialog.GetName()); + emit AmiiboSettingsFinished(true, cabinet_applet->GetName()); +} + +void GMainWindow::AmiiboSettingsRequestExit() { + if (cabinet_applet) { + cabinet_applet->reject(); + } } void GMainWindow::ControllerSelectorReconfigureControllers( const Core::Frontend::ControllerParameters& parameters) { - QtControllerSelectorDialog dialog(this, parameters, input_subsystem.get(), *system); - - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint); - dialog.setWindowModality(Qt::WindowModal); - dialog.exec(); + controller_applet = + new QtControllerSelectorDialog(this, parameters, input_subsystem.get(), *system); + SCOPE_EXIT({ + controller_applet->deleteLater(); + controller_applet = nullptr; + }); - emit ControllerSelectorReconfigureFinished(); + controller_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | + Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint); + controller_applet->setWindowModality(Qt::WindowModal); + bool is_success = controller_applet->exec() != QDialog::Rejected; // Don't forget to apply settings. + system->HIDCore().DisableAllControllerConfiguration(); system->ApplySettings(); config->Save(); UpdateStatusButtons(); + + emit ControllerSelectorReconfigureFinished(is_success); } -void GMainWindow::ProfileSelectorSelectProfile() { - QtProfileSelectionDialog dialog(system->HIDCore(), this); - dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint | - Qt::WindowTitleHint | Qt::WindowSystemMenuHint | - Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::WindowModal); - if (dialog.exec() == QDialog::Rejected) { +void GMainWindow::ControllerSelectorRequestExit() { + if (controller_applet) { + controller_applet->reject(); + } +} + +void GMainWindow::ProfileSelectorSelectProfile( + const Core::Frontend::ProfileSelectParameters& parameters) { + profile_select_applet = new QtProfileSelectionDialog(system->HIDCore(), this, parameters); + SCOPE_EXIT({ + profile_select_applet->deleteLater(); + profile_select_applet = nullptr; + }); + + profile_select_applet->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | + Qt::WindowStaysOnTopHint | Qt::WindowTitleHint | + Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); + profile_select_applet->setWindowModality(Qt::WindowModal); + if (profile_select_applet->exec() == QDialog::Rejected) { emit ProfileSelectorFinishedSelection(std::nullopt); return; } const Service::Account::ProfileManager manager; - const auto uuid = manager.GetUser(static_cast<std::size_t>(dialog.GetIndex())); + const auto uuid = manager.GetUser(static_cast<std::size_t>(profile_select_applet->GetIndex())); if (!uuid.has_value()) { emit ProfileSelectorFinishedSelection(std::nullopt); return; @@ -639,6 +695,12 @@ void GMainWindow::ProfileSelectorSelectProfile() { emit ProfileSelectorFinishedSelection(uuid); } +void GMainWindow::ProfileSelectorRequestExit() { + if (profile_select_applet) { + profile_select_applet->reject(); + } +} + void GMainWindow::SoftwareKeyboardInitialize( bool is_inline, Core::Frontend::KeyboardInitializeParameters initialize_parameters) { if (software_keyboard) { @@ -680,8 +742,10 @@ void GMainWindow::SoftwareKeyboardShowNormal() { const auto y = layout.screen.top; const auto w = layout.screen.GetWidth(); const auto h = layout.screen.GetHeight(); + const auto scale_ratio = devicePixelRatioF(); - software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); + software_keyboard->ShowNormalKeyboard(render_window->mapToGlobal(QPoint(x, y) / scale_ratio), + QSize(w, h) / scale_ratio); } void GMainWindow::SoftwareKeyboardShowTextCheck( @@ -714,9 +778,11 @@ void GMainWindow::SoftwareKeyboardShowInline( (1.0f - appear_parameters.key_top_scale_y)))); const auto w = static_cast<int>(layout.screen.GetWidth() * appear_parameters.key_top_scale_x); const auto h = static_cast<int>(layout.screen.GetHeight() * appear_parameters.key_top_scale_y); + const auto scale_ratio = devicePixelRatioF(); software_keyboard->ShowInlineKeyboard(std::move(appear_parameters), - render_window->mapToGlobal(QPoint(x, y)), QSize(w, h)); + render_window->mapToGlobal(QPoint(x, y) / scale_ratio), + QSize(w, h) / scale_ratio); } void GMainWindow::SoftwareKeyboardHideInline() { @@ -759,7 +825,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, return; } - QtNXWebEngineView web_browser_view(this, *system, input_subsystem.get()); + web_applet = new QtNXWebEngineView(this, *system, input_subsystem.get()); ui->action_Pause->setEnabled(false); ui->action_Restart->setEnabled(false); @@ -786,9 +852,9 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, loading_progress.setValue(1); if (is_local) { - web_browser_view.LoadLocalWebPage(main_url, additional_args); + web_applet->LoadLocalWebPage(main_url, additional_args); } else { - web_browser_view.LoadExternalWebPage(main_url, additional_args); + web_applet->LoadExternalWebPage(main_url, additional_args); } if (render_window->IsLoadingComplete()) { @@ -796,13 +862,16 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, } const auto& layout = render_window->GetFramebufferLayout(); - web_browser_view.resize(layout.screen.GetWidth(), layout.screen.GetHeight()); - web_browser_view.move(layout.screen.left, layout.screen.top + menuBar()->height()); - web_browser_view.setZoomFactor(static_cast<qreal>(layout.screen.GetWidth()) / - static_cast<qreal>(Layout::ScreenUndocked::Width)); + const auto scale_ratio = devicePixelRatioF(); + web_applet->resize(layout.screen.GetWidth() / scale_ratio, + layout.screen.GetHeight() / scale_ratio); + web_applet->move(layout.screen.left / scale_ratio, + (layout.screen.top / scale_ratio) + menuBar()->height()); + web_applet->setZoomFactor(static_cast<qreal>(layout.screen.GetWidth() / scale_ratio) / + static_cast<qreal>(Layout::ScreenUndocked::Width)); - web_browser_view.setFocus(); - web_browser_view.show(); + web_applet->setFocus(); + web_applet->show(); loading_progress.setValue(2); @@ -815,7 +884,7 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, // TODO (Morph): Remove this QAction* exit_action = new QAction(tr("Disable Web Applet"), this); - connect(exit_action, &QAction::triggered, this, [this, &web_browser_view] { + connect(exit_action, &QAction::triggered, this, [this] { const auto result = QMessageBox::warning( this, tr("Disable Web Applet"), tr("Disabling the web applet can lead to undefined behavior and should only be used " @@ -824,21 +893,21 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, QMessageBox::Yes | QMessageBox::No); if (result == QMessageBox::Yes) { UISettings::values.disable_web_applet = true; - web_browser_view.SetFinished(true); + web_applet->SetFinished(true); } }); ui->menubar->addAction(exit_action); - while (!web_browser_view.IsFinished()) { + while (!web_applet->IsFinished()) { QCoreApplication::processEvents(); if (!exit_check) { - web_browser_view.page()->runJavaScript( + web_applet->page()->runJavaScript( QStringLiteral("end_applet;"), [&](const QVariant& variant) { exit_check = false; if (variant.toBool()) { - web_browser_view.SetFinished(true); - web_browser_view.SetExitReason( + web_applet->SetFinished(true); + web_applet->SetExitReason( Service::AM::Applets::WebExitReason::EndButtonPressed); } }); @@ -846,22 +915,22 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, exit_check = true; } - if (web_browser_view.GetCurrentURL().contains(QStringLiteral("localhost"))) { - if (!web_browser_view.IsFinished()) { - web_browser_view.SetFinished(true); - web_browser_view.SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); + if (web_applet->GetCurrentURL().contains(QStringLiteral("localhost"))) { + if (!web_applet->IsFinished()) { + web_applet->SetFinished(true); + web_applet->SetExitReason(Service::AM::Applets::WebExitReason::CallbackURL); } - web_browser_view.SetLastURL(web_browser_view.GetCurrentURL().toStdString()); + web_applet->SetLastURL(web_applet->GetCurrentURL().toStdString()); } std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - const auto exit_reason = web_browser_view.GetExitReason(); - const auto last_url = web_browser_view.GetLastURL(); + const auto exit_reason = web_applet->GetExitReason(); + const auto last_url = web_applet->GetLastURL(); - web_browser_view.hide(); + web_applet->hide(); render_window->setFocus(); @@ -887,6 +956,15 @@ void GMainWindow::WebBrowserOpenWebPage(const std::string& main_url, #endif } +void GMainWindow::WebBrowserRequestExit() { +#ifdef YUZU_USE_QT_WEB_ENGINE + if (web_applet) { + web_applet->SetExitReason(Service::AM::Applets::WebExitReason::ExitRequested); + web_applet->SetFinished(true); + } +#endif +} + void GMainWindow::InitializeWidgets() { #ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING ui->action_Report_Compatibility->setVisible(true); @@ -957,6 +1035,56 @@ void GMainWindow::InitializeWidgets() { tas_label->setFocusPolicy(Qt::NoFocus); statusBar()->insertPermanentWidget(0, tas_label); + 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(200); + volume_slider->setPageStep(5); + connect(volume_slider, &QSlider::valueChanged, this, [this](int percentage) { + Settings::values.audio_muted = false; + const auto volume = static_cast<u8>(percentage); + Settings::values.volume.SetValue(volume); + 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()))); + }); + volume_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(volume_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + context_menu.addAction( + Settings::values.audio_muted ? tr("Unmute") : tr("Mute"), [this] { + Settings::values.audio_muted = !Settings::values.audio_muted; + UpdateVolumeUI(); + }); + + context_menu.addAction(tr("Reset Volume"), [this] { + Settings::values.volume.SetValue(100); + UpdateVolumeUI(); + }); + + context_menu.exec(volume_button->mapToGlobal(menu_location)); + volume_button->repaint(); + }); + statusBar()->insertPermanentWidget(0, volume_button); + // setup AA button aa_status_button = new QPushButton(); aa_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); @@ -975,6 +1103,19 @@ void GMainWindow::InitializeWidgets() { UpdateAAText(); aa_status_button->setCheckable(true); aa_status_button->setChecked(true); + aa_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(aa_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + for (auto const& aa_text_pair : Config::anti_aliasing_texts_map) { + context_menu.addAction(aa_text_pair.second, [this, aa_text_pair] { + Settings::values.anti_aliasing.SetValue(aa_text_pair.first); + UpdateAAText(); + }); + } + context_menu.exec(aa_status_button->mapToGlobal(menu_location)); + aa_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, aa_status_button); // Setup Filter button @@ -983,14 +1124,22 @@ void GMainWindow::InitializeWidgets() { filter_status_button->setFocusPolicy(Qt::NoFocus); connect(filter_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleAdaptingFilter); - auto filter = Settings::values.scaling_filter.GetValue(); - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && - filter == Settings::ScalingFilter::Fsr) { - Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor); - } UpdateFilterText(); filter_status_button->setCheckable(true); filter_status_button->setChecked(true); + filter_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(filter_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + for (auto const& filter_text_pair : Config::scaling_filter_texts_map) { + context_menu.addAction(filter_text_pair.second, [this, filter_text_pair] { + Settings::values.scaling_filter.SetValue(filter_text_pair.first); + UpdateFilterText(); + }); + } + context_menu.exec(filter_status_button->mapToGlobal(menu_location)); + filter_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, filter_status_button); // Setup Dock button @@ -1000,14 +1149,47 @@ void GMainWindow::InitializeWidgets() { connect(dock_status_button, &QPushButton::clicked, this, &GMainWindow::OnToggleDockedMode); dock_status_button->setCheckable(true); UpdateDockedButton(); + dock_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(dock_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& docked_mode_pair : Config::use_docked_mode_texts_map) { + context_menu.addAction(docked_mode_pair.second, [this, docked_mode_pair] { + if (docked_mode_pair.first != Settings::values.use_docked_mode.GetValue()) { + OnToggleDockedMode(); + } + }); + } + context_menu.exec(dock_status_button->mapToGlobal(menu_location)); + dock_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, dock_status_button); + // Setup GPU Accuracy button gpu_accuracy_button = new QPushButton(); gpu_accuracy_button->setObjectName(QStringLiteral("GPUStatusBarButton")); gpu_accuracy_button->setCheckable(true); gpu_accuracy_button->setFocusPolicy(Qt::NoFocus); connect(gpu_accuracy_button, &QPushButton::clicked, this, &GMainWindow::OnToggleGpuAccuracy); UpdateGPUAccuracyButton(); + gpu_accuracy_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(gpu_accuracy_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& gpu_accuracy_pair : Config::gpu_accuracy_texts_map) { + if (gpu_accuracy_pair.first == Settings::GPUAccuracy::Extreme) { + continue; + } + context_menu.addAction(gpu_accuracy_pair.second, [this, gpu_accuracy_pair] { + Settings::values.gpu_accuracy.SetValue(gpu_accuracy_pair.first); + UpdateGPUAccuracyButton(); + }); + } + context_menu.exec(gpu_accuracy_button->mapToGlobal(menu_location)); + gpu_accuracy_button->repaint(); + }); statusBar()->insertPermanentWidget(0, gpu_accuracy_button); // Setup Renderer API button @@ -1020,6 +1202,24 @@ void GMainWindow::InitializeWidgets() { renderer_status_button->setCheckable(true); renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::Vulkan); + renderer_status_button->setContextMenuPolicy(Qt::CustomContextMenu); + connect(renderer_status_button, &QPushButton::customContextMenuRequested, + [this](const QPoint& menu_location) { + QMenu context_menu; + + for (auto const& renderer_backend_pair : Config::renderer_backend_texts_map) { + if (renderer_backend_pair.first == Settings::RendererBackend::Null) { + continue; + } + context_menu.addAction( + renderer_backend_pair.second, [this, renderer_backend_pair] { + Settings::values.renderer_backend.SetValue(renderer_backend_pair.first); + UpdateAPIText(); + }); + } + context_menu.exec(renderer_status_button->mapToGlobal(menu_location)); + renderer_status_button->repaint(); + }); statusBar()->insertPermanentWidget(0, renderer_status_button); statusBar()->setVisible(true); @@ -1070,7 +1270,8 @@ void GMainWindow::InitializeRecentFileMenuActions() { UpdateRecentFiles(); } -void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name) { +void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name, + const bool tas_allowed) { static const QString main_window = QStringLiteral("Main Window"); action->setShortcut(hotkey_registry.GetKeySequence(main_window, action_name)); action->setShortcutContext(hotkey_registry.GetShortcutContext(main_window, action_name)); @@ -1082,7 +1283,14 @@ void GMainWindow::LinkActionShortcut(QAction* action, const QString& action_name const auto* controller_hotkey = hotkey_registry.GetControllerHotkey(main_window, action_name, controller); connect( - controller_hotkey, &ControllerShortcut::Activated, this, [action] { action->trigger(); }, + controller_hotkey, &ControllerShortcut::Activated, this, + [action, tas_allowed, this] { + auto [tas_status, current_tas_frame, total_tas_frames] = + input_subsystem->GetTas()->GetStatus(); + if (tas_allowed || tas_status == InputCommon::TasInput::TasState::Stopped) { + action->trigger(); + } + }, Qt::QueuedConnection); } @@ -1099,9 +1307,9 @@ void GMainWindow::InitializeHotkeys() { LinkActionShortcut(ui->action_Show_Status_Bar, QStringLiteral("Toggle Status Bar")); LinkActionShortcut(ui->action_Fullscreen, QStringLiteral("Fullscreen")); LinkActionShortcut(ui->action_Capture_Screenshot, QStringLiteral("Capture Screenshot")); - LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop")); - LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record")); - LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset")); + LinkActionShortcut(ui->action_TAS_Start, QStringLiteral("TAS Start/Stop"), true); + LinkActionShortcut(ui->action_TAS_Record, QStringLiteral("TAS Record"), true); + LinkActionShortcut(ui->action_TAS_Reset, QStringLiteral("TAS Reset"), true); static const QString main_window = QStringLiteral("Main Window"); const auto connect_shortcut = [&]<typename Fn>(const QString& action_name, const Fn& function) { @@ -1124,34 +1332,21 @@ void GMainWindow::InitializeHotkeys() { &GMainWindow::OnToggleAdaptingFilter); connect_shortcut(QStringLiteral("Change Docked Mode"), &GMainWindow::OnToggleDockedMode); connect_shortcut(QStringLiteral("Change GPU Accuracy"), &GMainWindow::OnToggleGpuAccuracy); - 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<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<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("Audio Mute/Unmute"), &GMainWindow::OnMute); + connect_shortcut(QStringLiteral("Audio Volume Down"), &GMainWindow::OnDecreaseVolume); + connect_shortcut(QStringLiteral("Audio Volume Up"), &GMainWindow::OnIncreaseVolume); connect_shortcut(QStringLiteral("Toggle Framerate Limit"), [] { Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue()); }); connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] { + if (Settings::values.mouse_enabled) { + Settings::values.mouse_panning = false; + QMessageBox::warning( + this, tr("Emulated mouse is enabled"), + tr("Real mouse input and mouse panning are incompatible. Please disable the " + "emulated mouse in input advanced settings to allow mouse panning.")); + return; + } Settings::values.mouse_panning = !Settings::values.mouse_panning; if (Settings::values.mouse_panning) { render_window->installEventFilter(render_window); @@ -1258,6 +1453,7 @@ void GMainWindow::ConnectWidgetEvents() { connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList); connect(game_list, &GameList::PopulatingCompleted, [this] { multiplayer_state->UpdateGameList(game_list->GetModel()); }); + connect(game_list, &GameList::SaveConfig, this, &GMainWindow::OnSaveConfig); connect(game_list, &GameList::OpenPerGameGeneralRequested, this, &GMainWindow::OnGameListOpenPerGameProperties); @@ -1435,45 +1631,6 @@ void GMainWindow::OnPrepareForSleep(bool prepare_sleep) { } #ifdef __unix__ -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(QString::fromLatin1("org.freedesktop.portal.Desktop"), - QString::fromLatin1("/org/freedesktop/portal/desktop"), - QString::fromLatin1("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 yuzu needs to prevent the - //: computer from sleeping - options.insert(QString::fromLatin1("reason"), - QCoreApplication::translate("GMainWindow", "yuzu is running a game")); - // 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(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")); -} - std::array<int, 3> GMainWindow::sig_interrupt_fds{0, 0, 0}; void GMainWindow::SetupSigInterrupts() { @@ -1526,12 +1683,6 @@ void GMainWindow::PreventOSSleep() { SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED); #elif defined(HAVE_SDL2) SDL_DisableScreenSaver(); -#ifdef __unix__ - auto reply = HoldWakeLockLinux(winId()); - if (reply) { - wake_lock = std::move(reply.value()); - } -#endif #endif } @@ -1540,11 +1691,6 @@ void GMainWindow::AllowOSSleep() { SetThreadExecutionState(ES_CONTINUOUS); #elif defined(HAVE_SDL2) SDL_EnableScreenSaver(); -#ifdef __unix__ - if (!wake_lock.path().isEmpty()) { - ReleaseWakeLockLinux(wake_lock); - } -#endif #endif } @@ -1645,8 +1791,9 @@ bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t p return true; } -bool GMainWindow::SelectAndSetCurrentUser() { - QtProfileSelectionDialog dialog(system->HIDCore(), this); +bool GMainWindow::SelectAndSetCurrentUser( + const Core::Frontend::ProfileSelectParameters& parameters) { + QtProfileSelectionDialog dialog(system->HIDCore(), this, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); @@ -1692,7 +1839,13 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t Settings::LogSettings(); if (UISettings::values.select_user_on_boot) { - if (SelectAndSetCurrentUser() == false) { + const Core::Frontend::ProfileSelectParameters parameters{ + .mode = Service::AM::Applets::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Applets::UserSelectionPurpose::General, + }; + if (SelectAndSetCurrentUser(parameters) == false) { return; } } @@ -1702,6 +1855,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t } system->SetShuttingDown(false); + game_list->setDisabled(true); // Create and start the emulation thread emu_thread = std::make_unique<EmuThread>(*system); @@ -1767,7 +1921,7 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t std::filesystem::path{Common::U16StringFromBuffer(filename.utf16(), filename.size())} .filename()); } - const bool is_64bit = system->Kernel().CurrentProcess()->Is64BitProcess(); + const bool is_64bit = system->Kernel().ApplicationProcess()->Is64BitProcess(); const auto instruction_set_suffix = is_64bit ? tr("(64-bit)") : tr("(32-bit)"); title_name = tr("%1 %2", "%1 is the title name. %2 indicates if the title is 64-bit or 32-bit") .arg(QString::fromStdString(title_name), instruction_set_suffix) @@ -1897,6 +2051,9 @@ void GMainWindow::OnEmulationStopped() { // When closing the game, destroy the GLWindow to clear the context after the game is closed render_window->ReleaseRenderTarget(); + // Enable game list + game_list->setEnabled(true); + Settings::RestoreGlobalState(system->IsPoweredOn()); system->HIDCore().ReloadInputDevices(); UpdateStatusButtons(); @@ -1984,7 +2141,13 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target if (has_user_save) { // User save data const auto select_profile = [this] { - QtProfileSelectionDialog dialog(system->HIDCore(), this); + const Core::Frontend::ProfileSelectParameters parameters{ + .mode = Service::AM::Applets::UiMode::UserSelector, + .invalid_uid_list = {}, + .display_options = {}, + .purpose = Service::AM::Applets::UserSelectionPurpose::General, + }; + QtProfileSelectionDialog dialog(system->HIDCore(), this, parameters); dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::WindowModal); @@ -2221,6 +2384,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ return tr("Delete All Transferable Shader Caches?"); case GameListRemoveTarget::CustomConfiguration: return tr("Remove Custom Game Configuration?"); + case GameListRemoveTarget::CacheStorage: + return tr("Remove Cache Storage?"); default: return QString{}; } @@ -2244,6 +2409,9 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ case GameListRemoveTarget::CustomConfiguration: RemoveCustomConfiguration(program_id, game_path); break; + case GameListRemoveTarget::CacheStorage: + RemoveCacheStorage(program_id); + break; } } @@ -2333,6 +2501,21 @@ void GMainWindow::RemoveCustomConfiguration(u64 program_id, const std::string& g } } +void GMainWindow::RemoveCacheStorage(u64 program_id) { + const auto nand_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir); + auto vfs_nand_dir = + vfs->OpenDirectory(Common::FS::PathToUTF8String(nand_dir), FileSys::Mode::Read); + + const auto cache_storage_path = FileSys::SaveDataFactory::GetFullPath( + *system, vfs_nand_dir, FileSys::SaveDataSpaceId::NandUser, + FileSys::SaveDataType::CacheStorage, 0 /* program_id */, {}, 0); + + const auto path = Common::FS::ConcatPathSafe(nand_dir, cache_storage_path); + + // Not an error if it wasn't cleared. + Common::FS::RemoveDirRecursively(path); +} + void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target) { const auto failed = [this] { @@ -2642,6 +2825,8 @@ void GMainWindow::OnGameListAddDirectory() { } else { LOG_WARNING(Frontend, "Selected directory is already in the game list"); } + + OnSaveConfig(); } void GMainWindow::OnGameListShowList(bool show) { @@ -2759,8 +2944,7 @@ void GMainWindow::OnMenuInstallToNAND() { ui->action_Install_File_NAND->setEnabled(false); install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this); - install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & - ~Qt::WindowMaximizeButtonHint); + install_progress->setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); install_progress->setAttribute(Qt::WA_DeleteOnClose, true); install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); install_progress->show(); @@ -2845,7 +3029,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { return false; } - std::array<u8, 0x1000> buffer{}; + std::vector<u8> buffer(1_MiB); for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { if (install_progress->wasCanceled()) { @@ -3004,8 +3188,10 @@ void GMainWindow::OnRestartGame() { if (!system->IsPoweredOn()) { return; } - // Make a copy since BootGame edits game_path - BootGame(QString(current_game_path)); + // Make a copy since ShutdownGame edits game_path + const auto current_game = QString(current_game_path); + ShutdownGame(); + BootGame(current_game); } void GMainWindow::OnPauseGame() { @@ -3056,13 +3242,23 @@ void GMainWindow::OnSaveConfig() { } 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); - dialog.exec(); + error_applet = new OverlayDialog(render_window, *system, error_code, error_text, QString{}, + tr("OK"), Qt::AlignLeft | Qt::AlignVCenter); + SCOPE_EXIT({ + error_applet->deleteLater(); + error_applet = nullptr; + }); + error_applet->exec(); emit ErrorDisplayFinished(); } +void GMainWindow::ErrorDisplayRequestExit() { + if (error_applet) { + error_applet->reject(); + } +} + void GMainWindow::OnMenuReportCompatibility() { #if defined(ARCHITECTURE_x86_64) && !defined(__APPLE__) const auto& caps = Common::GetCPUCaps(); @@ -3257,9 +3453,11 @@ void GMainWindow::ResetWindowSize1080() { void GMainWindow::OnConfigure() { const auto old_theme = UISettings::values.theme; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); + const auto old_language_index = Settings::values.language_index.GetValue(); Settings::SetConfiguringGlobal(true); - ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), *system, + ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), + vk_device_records, *system, !multiplayer_state->IsHostingPublicRoom()); connect(&configure_dialog, &ConfigureDialog::LanguageChanged, this, &GMainWindow::OnLanguageChanged); @@ -3325,7 +3523,7 @@ void GMainWindow::OnConfigure() { emit UpdateThemedIcons(); const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); - if (reload) { + if (reload || Settings::values.language_index.GetValue() != old_language_index) { game_list->PopulateAsync(UISettings::values.game_dirs); } @@ -3369,6 +3567,7 @@ void GMainWindow::OnConfigureTas() { return; } else if (result == QDialog::Accepted) { dialog.ApplyConfiguration(); + OnSaveConfig(); } } @@ -3463,6 +3662,39 @@ void GMainWindow::OnToggleGpuAccuracy() { UpdateGPUAccuracyButton(); } +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()); + 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)); + UpdateVolumeUI(); +} + +void GMainWindow::OnIncreaseVolume() { + Settings::values.audio_muted = false; + 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); + UpdateVolumeUI(); +} + void GMainWindow::OnToggleAdaptingFilter() { auto filter = Settings::values.scaling_filter.GetValue(); if (filter == Settings::ScalingFilter::LastFilter) { @@ -3470,10 +3702,6 @@ void GMainWindow::OnToggleAdaptingFilter() { } else { filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1); } - if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && - filter == Settings::ScalingFilter::Fsr) { - filter = Settings::ScalingFilter::NearestNeighbor; - } Settings::values.scaling_filter.SetValue(filter); filter_status_button->setChecked(true); UpdateFilterText(); @@ -3492,7 +3720,7 @@ void GMainWindow::OnToggleGraphicsAPI() { } void GMainWindow::OnConfigurePerGame() { - const u64 title_id = system->GetCurrentProcessProgramID(); + const u64 title_id = system->GetApplicationProcessProgramID(); OpenPerGameConfiguration(title_id, current_game_path.toStdString()); } @@ -3500,7 +3728,7 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file const auto v_file = Core::GetGameFileFromPath(vfs, file_name); Settings::SetConfiguringGlobal(false); - ConfigurePerGame dialog(this, title_id, file_name, *system); + ConfigurePerGame dialog(this, title_id, file_name, vk_device_records, *system); dialog.LoadFromFile(v_file); const auto result = dialog.exec(); @@ -3533,7 +3761,7 @@ bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::st const std::string& command, const std::string& arguments, const std::string& categories, const std::string& keywords) { #if defined(__linux__) || defined(__FreeBSD__) - // This desktop file template was writting referencing + // This desktop file template was writing referencing // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html std::string shortcut_contents{}; shortcut_contents.append("[Desktop Entry]\n"); @@ -3571,7 +3799,7 @@ void GMainWindow::OnLoadAmiibo() { auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); // Remove amiibo if one is connected - if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) { virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; @@ -3599,7 +3827,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { 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) { + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) { virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; @@ -3651,7 +3879,7 @@ void GMainWindow::OnCaptureScreenshot() { return; } - const u64 title_id = system->GetCurrentProcessProgramID(); + const u64 title_id = system->GetApplicationProcessProgramID(); const auto screenshot_path = QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::ScreenshotsDir)); const auto date = @@ -3835,93 +4063,55 @@ void GMainWindow::UpdateStatusBar() { } void GMainWindow::UpdateGPUAccuracyButton() { - switch (Settings::values.gpu_accuracy.GetValue()) { - case Settings::GPUAccuracy::Normal: { - gpu_accuracy_button->setText(tr("GPU NORMAL")); - gpu_accuracy_button->setChecked(false); - break; - } - case Settings::GPUAccuracy::High: { - gpu_accuracy_button->setText(tr("GPU HIGH")); - gpu_accuracy_button->setChecked(true); - break; - } - case Settings::GPUAccuracy::Extreme: { - gpu_accuracy_button->setText(tr("GPU EXTREME")); - gpu_accuracy_button->setChecked(true); - break; - } - default: { - gpu_accuracy_button->setText(tr("GPU ERROR")); - gpu_accuracy_button->setChecked(true); - break; - } - } + const auto gpu_accuracy = Settings::values.gpu_accuracy.GetValue(); + const auto gpu_accuracy_text = Config::gpu_accuracy_texts_map.find(gpu_accuracy)->second; + gpu_accuracy_button->setText(gpu_accuracy_text.toUpper()); + gpu_accuracy_button->setChecked(gpu_accuracy != Settings::GPUAccuracy::Normal); } 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")); + dock_status_button->setText( + Config::use_docked_mode_texts_map.find(is_docked)->second.toUpper()); } void GMainWindow::UpdateAPIText() { const auto api = Settings::values.renderer_backend.GetValue(); - switch (api) { - case Settings::RendererBackend::OpenGL: - renderer_status_button->setText(tr("OPENGL")); - break; - case Settings::RendererBackend::Vulkan: - renderer_status_button->setText(tr("VULKAN")); - break; - case Settings::RendererBackend::Null: - renderer_status_button->setText(tr("NULL")); - break; - } + const auto renderer_status_text = Config::renderer_backend_texts_map.find(api)->second; + renderer_status_button->setText( + api == Settings::RendererBackend::OpenGL + ? tr("%1 %2").arg( + renderer_status_text.toUpper(), + Config::shader_backend_texts_map.find(Settings::values.shader_backend.GetValue()) + ->second) + : renderer_status_text.toUpper()); } void GMainWindow::UpdateFilterText() { const auto filter = Settings::values.scaling_filter.GetValue(); - switch (filter) { - case Settings::ScalingFilter::NearestNeighbor: - filter_status_button->setText(tr("NEAREST")); - break; - case Settings::ScalingFilter::Bilinear: - filter_status_button->setText(tr("BILINEAR")); - break; - case Settings::ScalingFilter::Bicubic: - filter_status_button->setText(tr("BICUBIC")); - break; - case Settings::ScalingFilter::Gaussian: - filter_status_button->setText(tr("GAUSSIAN")); - break; - case Settings::ScalingFilter::ScaleForce: - filter_status_button->setText(tr("SCALEFORCE")); - break; - case Settings::ScalingFilter::Fsr: - filter_status_button->setText(tr("FSR")); - break; - default: - filter_status_button->setText(tr("BILINEAR")); - break; - } + const auto filter_text = Config::scaling_filter_texts_map.find(filter)->second; + filter_status_button->setText(filter == Settings::ScalingFilter::Fsr ? tr("FSR") + : filter_text.toUpper()); } void GMainWindow::UpdateAAText() { const auto aa_mode = Settings::values.anti_aliasing.GetValue(); - switch (aa_mode) { - case Settings::AntiAliasing::None: - aa_status_button->setText(tr("NO AA")); - break; - case Settings::AntiAliasing::Fxaa: - aa_status_button->setText(tr("FXAA")); - break; - case Settings::AntiAliasing::Smaa: - aa_status_button->setText(tr("SMAA")); - break; - default: - aa_status_button->setText(tr("NO AA")); - break; + const auto aa_text = Config::anti_aliasing_texts_map.find(aa_mode)->second; + aa_status_button->setText(aa_mode == Settings::AntiAliasing::None + ? QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "NO AA")) + : aa_text.toUpper()); +} + +void GMainWindow::UpdateVolumeUI() { + const auto volume_value = static_cast<int>(Settings::values.volume.GetValue()); + 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)); } } @@ -3933,6 +4123,7 @@ void GMainWindow::UpdateStatusButtons() { UpdateDockedButton(); UpdateFilterText(); UpdateAAText(); + UpdateVolumeUI(); } void GMainWindow::UpdateUISettings() { @@ -4022,6 +4213,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { } Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); + bool all_keys_present{true}; + if (keys.BaseDeriveNecessary()) { Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)}; @@ -4046,6 +4239,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { errors += tr(" - Missing PRODINFO"); } if (!errors.isEmpty()) { + all_keys_present = false; QMessageBox::warning( this, tr("Derivation Components Missing"), tr("Encryption keys are missing. " @@ -4073,11 +4267,40 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { system->GetFileSystemController().CreateFactories(*vfs); + if (all_keys_present && !this->CheckSystemArchiveDecryption()) { + LOG_WARNING(Frontend, "Mii model decryption failed"); + QMessageBox::warning( + this, tr("System Archive Decryption Failed"), + tr("Encryption keys failed to decrypt firmware. " + "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu " + "quickstart guide</a> to get all your keys, firmware and " + "games.")); + } + if (behavior == ReinitializeKeyBehavior::Warning) { game_list->PopulateAsync(UISettings::values.game_dirs); } } +bool GMainWindow::CheckSystemArchiveDecryption() { + constexpr u64 MiiModelId = 0x0100000000000802; + + auto bis_system = system->GetFileSystemController().GetSystemNANDContents(); + if (!bis_system) { + // Not having system BIS files is not an error. + return true; + } + + auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data); + if (!mii_nca) { + // Not having the Mii model is not an error. + return true; + } + + // Return whether we are able to decrypt the RomFS of the Mii model. + return mii_nca->GetRomFS().get() != nullptr; +} + std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed, u64 program_id) { const auto dlc_entries = @@ -4402,6 +4625,55 @@ void GMainWindow::changeEvent(QEvent* event) { #undef main #endif +static void SetHighDPIAttributes() { +#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 == nullptr) { + return; + } + + const QRect screen_rect = primary_screen->geometry(); + const int real_width = screen_rect.width(); + const int real_height = screen_rect.height(); + const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f; + + // Recommended minimum width and height for proper window fit. + // Any screen with a lower resolution than this will still have a scale of 1. + constexpr float minimum_width = 1350.0f; + constexpr float minimum_height = 900.0f; + + const float width_ratio = std::max(1.0f, real_width / minimum_width); + const float height_ratio = std::max(1.0f, real_height / minimum_height); + + // Get the lower of the 2 ratios and truncate, this is the maximum integer scale. + const float max_ratio = std::trunc(std::min(width_ratio, height_ratio)); + + if (max_ratio > real_ratio) { + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::Round); + } else { + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::Floor); + } +#else + // Other OSes should be better than Windows at fractional scaling. + QApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif + + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +} + int main(int argc, char* argv[]) { std::unique_ptr<Config> config = std::make_unique<Config>(); bool has_broken_vulkan = false; @@ -4457,8 +4729,16 @@ int main(int argc, char* argv[]) { } #endif + SetHighDPIAttributes(); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // Disables the "?" button on all dialogs. Disabled by default on Qt6. + QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); +#endif + // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); + QApplication app(argc, argv); #ifdef _WIN32 |