summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CMakeLists.txt45
-rw-r--r--src/android/app/build.gradle.kts16
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt25
-rw-r--r--src/android/build.gradle.kts4
-rw-r--r--src/audio_core/sink/sink_stream.cpp4
-rw-r--r--src/common/polyfill_thread.h21
-rw-r--r--src/common/settings.cpp5
-rw-r--r--src/common/settings.h5
-rw-r--r--src/common/settings_enums.h2
-rw-r--r--src/video_core/engines/draw_manager.cpp24
-rw-r--r--src/video_core/engines/draw_manager.h2
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_input.cpp36
-rw-r--r--src/yuzu/configuration/configure_input.h1
-rw-r--r--src/yuzu/configuration/shared_translation.cpp8
-rw-r--r--src/yuzu/main.cpp115
-rw-r--r--src/yuzu/main.h18
-rw-r--r--src/yuzu/uisettings.h11
20 files changed, 220 insertions, 129 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2bef9d6ed..166024844 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,7 +11,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modul
include(DownloadExternals)
include(CMakeDependentOption)
include(CTest)
-include(FetchContent)
# Set bundled sdl2/qt as dependent options.
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
@@ -99,47 +98,8 @@ if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
DESTINATION "${vvl_lib_path}")
endif()
-# On Android, fetch and compile libcxx before doing anything else
if (ANDROID)
set(CMAKE_SKIP_INSTALL_RULES ON)
- set(LLVM_VERSION "15.0.6")
-
- # Note: even though libcxx and libcxxabi have separate releases on the project page,
- # the separated releases cannot be compiled. Only in-tree builds work. Therefore we
- # must fetch the source release for the entire llvm tree.
- FetchContent_Declare(llvm
- URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/llvm-project-${LLVM_VERSION}.src.tar.xz"
- URL_HASH SHA256=9d53ad04dc60cb7b30e810faf64c5ab8157dadef46c8766f67f286238256ff92
- TLS_VERIFY TRUE
- )
- FetchContent_MakeAvailable(llvm)
-
- # libcxx has support for most of the range library, but it's gated behind a flag:
- add_compile_definitions(_LIBCPP_ENABLE_EXPERIMENTAL)
-
- # Disable standard header inclusion
- set(ANDROID_STL "none")
-
- # libcxxabi
- set(LIBCXXABI_INCLUDE_TESTS OFF)
- set(LIBCXXABI_ENABLE_SHARED FALSE)
- set(LIBCXXABI_ENABLE_STATIC TRUE)
- set(LIBCXXABI_LIBCXX_INCLUDES "${LIBCXX_TARGET_INCLUDE_DIRECTORY}" CACHE STRING "" FORCE)
- add_subdirectory("${llvm_SOURCE_DIR}/libcxxabi" "${llvm_BINARY_DIR}/libcxxabi")
- link_libraries(cxxabi_static)
-
- # libcxx
- set(LIBCXX_ABI_NAMESPACE "__ndk1" CACHE STRING "" FORCE)
- set(LIBCXX_CXX_ABI "libcxxabi")
- set(LIBCXX_INCLUDE_TESTS OFF)
- set(LIBCXX_INCLUDE_BENCHMARKS OFF)
- set(LIBCXX_INCLUDE_DOCS OFF)
- set(LIBCXX_ENABLE_SHARED FALSE)
- set(LIBCXX_ENABLE_STATIC TRUE)
- set(LIBCXX_ENABLE_ASSERTIONS FALSE)
- add_subdirectory("${llvm_SOURCE_DIR}/libcxx" "${llvm_BINARY_DIR}/libcxx")
- set_target_properties(cxx-headers PROPERTIES INTERFACE_COMPILE_OPTIONS "-isystem${CMAKE_BINARY_DIR}/${LIBCXX_INSTALL_INCLUDE_DIR}")
- link_libraries(cxx_static cxx-headers)
endif()
if (YUZU_USE_BUNDLED_VCPKG)
@@ -329,7 +289,7 @@ find_package(Boost 1.79.0 REQUIRED context)
find_package(enet 1.3 MODULE)
find_package(fmt 9 REQUIRED)
find_package(inih 52 MODULE COMPONENTS INIReader)
-find_package(LLVM 17 MODULE COMPONENTS Demangle)
+find_package(LLVM 17.0.2 MODULE COMPONENTS Demangle)
find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3 MODULE)
@@ -400,6 +360,9 @@ function(set_yuzu_qt_components)
if (ENABLE_QT_TRANSLATION)
list(APPEND YUZU_QT_COMPONENTS2 LinguistTools)
endif()
+ if (USE_DISCORD_PRESENCE)
+ list(APPEND YUZU_QT_COMPONENTS2 Network)
+ endif()
set(YUZU_QT_COMPONENTS ${YUZU_QT_COMPONENTS2} PARENT_SCOPE)
endfunction(set_yuzu_qt_components)
diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts
index 84a3308b7..ac43d84b7 100644
--- a/src/android/app/build.gradle.kts
+++ b/src/android/app/build.gradle.kts
@@ -27,7 +27,7 @@ android {
namespace = "org.yuzu.yuzu_emu"
compileSdkVersion = "android-34"
- ndkVersion = "25.2.9519653"
+ ndkVersion = "26.1.10909125"
buildFeatures {
viewBinding = true
@@ -203,23 +203,23 @@ ktlint {
}
dependencies {
- implementation("androidx.core:core-ktx:1.10.1")
+ implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.recyclerview:recyclerview:1.3.0")
+ implementation("androidx.recyclerview:recyclerview:1.3.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
- implementation("androidx.fragment:fragment-ktx:1.6.0")
+ implementation("androidx.fragment:fragment-ktx:1.6.1")
implementation("androidx.documentfile:documentfile:1.0.1")
implementation("com.google.android.material:material:1.9.0")
- implementation("androidx.preference:preference:1.2.0")
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+ implementation("androidx.preference:preference-ktx:1.2.1")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("io.coil-kt:coil:2.2.2")
implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.window:window:1.2.0-beta03")
implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
- implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
- implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
+ implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
}
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 832c08e15..a67351727 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
android:appCategory="game"
android:localeConfig="@xml/locales_config"
android:banner="@drawable/tv_banner"
- android:extractNativeLibs="true"
android:fullBackupContent="@xml/data_extraction_rules"
android:dataExtractionRules="@xml/data_extraction_rules_api_31"
android:enableOnBackInvokedCallback="true">
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index 10b1d3547..df21d74b2 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -26,6 +26,7 @@ import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
+import java.io.File
import java.io.IOException
class DriverManagerFragment : Fragment() {
@@ -154,29 +155,29 @@ class DriverManagerFragment : Fragment() {
R.string.installing_driver,
false
) {
+ val driverPath =
+ "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}"
+ val driverFile = File(driverPath)
+
// Ignore file exceptions when a user selects an invalid zip
try {
- GpuDriverHelper.copyDriverToInternalStorage(result)
+ if (!GpuDriverHelper.copyDriverToInternalStorage(result)) {
+ throw IOException("Driver failed validation!")
+ }
} catch (_: IOException) {
+ if (driverFile.exists()) {
+ driverFile.delete()
+ }
return@newInstance getString(R.string.select_gpu_driver_error)
}
- val driverData = GpuDriverHelper.customDriverData
- if (driverData.name == null) {
- return@newInstance getString(R.string.select_gpu_driver_error)
- }
-
+ val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
val driverInList =
driverViewModel.driverList.value.firstOrNull { it.second == driverData }
if (driverInList != null) {
return@newInstance getString(R.string.driver_already_installed)
} else {
- driverViewModel.addDriver(
- Pair(
- "${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}",
- driverData
- )
- )
+ driverViewModel.addDriver(Pair(driverPath, driverData))
driverViewModel.setNewDriverInstalled(true)
}
return@newInstance Any()
diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts
index 80f370c16..51e559321 100644
--- a/src/android/build.gradle.kts
+++ b/src/android/build.gradle.kts
@@ -3,8 +3,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id("com.android.application") version "8.0.2" apply false
- id("com.android.library") version "8.0.2" apply false
+ id("com.android.application") version "8.1.2" apply false
+ id("com.android.library") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
}
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
index 6081352a2..d66d04fae 100644
--- a/src/audio_core/sink/sink_stream.cpp
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -204,6 +204,10 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz
// paused and we'll desync, so just play silence.
if (system.IsPaused() || system.IsShuttingDown()) {
if (system.IsShuttingDown()) {
+ {
+ std::scoped_lock lk{release_mutex};
+ queued_buffers.store(0);
+ }
release_cv.notify_one();
}
diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h
index 41cbb9ed5..12e59a893 100644
--- a/src/common/polyfill_thread.h
+++ b/src/common/polyfill_thread.h
@@ -15,12 +15,13 @@
#include <condition_variable>
#include <stop_token>
#include <thread>
+#include <utility>
namespace Common {
template <typename Condvar, typename Lock, typename Pred>
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
- cv.wait(lk, token, std::move(pred));
+ cv.wait(lk, token, std::forward<Pred>(pred));
}
template <typename Rep, typename Period>
@@ -109,7 +110,7 @@ public:
// Insert the callback.
stop_state_callback ret = ++m_next_callback;
- m_callbacks.emplace(ret, move(f));
+ m_callbacks.emplace(ret, std::move(f));
return ret;
}
@@ -162,7 +163,7 @@ private:
friend class stop_source;
template <typename Callback>
friend class stop_callback;
- stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(move(stop_state)) {}
+ stop_token(shared_ptr<polyfill::stop_state> stop_state) : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
@@ -198,7 +199,7 @@ public:
private:
friend class jthread;
explicit stop_source(shared_ptr<polyfill::stop_state> stop_state)
- : m_stop_state(move(stop_state)) {}
+ : m_stop_state(std::move(stop_state)) {}
private:
shared_ptr<polyfill::stop_state> m_stop_state;
@@ -218,16 +219,16 @@ public:
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
: m_stop_state(st.m_stop_state) {
if (m_stop_state) {
- m_callback = m_stop_state->insert_callback(move(cb));
+ m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
template <typename C>
requires constructible_from<Callback, C>
explicit stop_callback(stop_token&& st,
C&& cb) noexcept(is_nothrow_constructible_v<Callback, C>)
- : m_stop_state(move(st.m_stop_state)) {
+ : m_stop_state(std::move(st.m_stop_state)) {
if (m_stop_state) {
- m_callback = m_stop_state->insert_callback(move(cb));
+ m_callback = m_stop_state->insert_callback(std::move(cb));
}
}
~stop_callback() {
@@ -260,7 +261,7 @@ public:
typename = enable_if_t<!is_same_v<remove_cvref_t<F>, jthread>>>
explicit jthread(F&& f, Args&&... args)
: m_stop_state(make_shared<polyfill::stop_state>()),
- m_thread(make_thread(move(f), move(args)...)) {}
+ m_thread(make_thread(std::forward<F>(f), std::forward<Args>(args)...)) {}
~jthread() {
if (joinable()) {
@@ -317,9 +318,9 @@ private:
template <typename F, typename... Args>
thread make_thread(F&& f, Args&&... args) {
if constexpr (is_invocable_v<decay_t<F>, stop_token, decay_t<Args>...>) {
- return thread(move(f), get_stop_token(), move(args)...);
+ return thread(std::forward<F>(f), get_stop_token(), std::forward<Args>(args)...);
} else {
- return thread(move(f), move(args)...);
+ return thread(std::forward<F>(f), std::forward<Args>(args)...);
}
}
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 3fde3cae6..98b43e49c 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -45,6 +45,7 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true);
+SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true);
@@ -61,6 +62,10 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false);
SWITCHABLE(u8, true);
+// Used in UISettings
+// TODO see if we can move this to uisettings.cpp
+SWITCHABLE(ConfirmStop, true);
+
#undef SETTING
#undef SWITCHABLE
#endif
diff --git a/src/common/settings.h b/src/common/settings.h
index 98ab0ec2e..236e33bee 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -67,6 +67,7 @@ SWITCHABLE(CpuAccuracy, true);
SWITCHABLE(FullscreenMode, true);
SWITCHABLE(GpuAccuracy, true);
SWITCHABLE(Language, true);
+SWITCHABLE(MemoryLayout, true);
SWITCHABLE(NvdecEmulation, false);
SWITCHABLE(Region, true);
SWITCHABLE(RendererBackend, true);
@@ -83,6 +84,10 @@ SWITCHABLE(u32, false);
SWITCHABLE(u8, false);
SWITCHABLE(u8, true);
+// Used in UISettings
+// TODO see if we can move this to uisettings.h
+SWITCHABLE(ConfirmStop, true);
+
#undef SETTING
#undef SWITCHABLE
#endif
diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h
index 815cafe15..11429d7a8 100644
--- a/src/common/settings_enums.h
+++ b/src/common/settings_enums.h
@@ -133,6 +133,8 @@ ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid);
ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb);
+ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never);
+
ENUM(FullscreenMode, Borderless, Exclusive);
ENUM(NvdecEmulation, Off, Cpu, Gpu);
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp
index f34090791..d77ff455b 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -48,8 +48,14 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
+ DrawArrayInstanced(regs.vertex_array_instance_first.topology.Value(),
+ regs.vertex_array_instance_first.start.Value(),
+ regs.vertex_array_instance_first.count.Value(), false);
+ break;
case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
- LOG_WARNING(HW_GPU, "(STUBBED) called");
+ DrawArrayInstanced(regs.vertex_array_instance_subsequent.topology.Value(),
+ regs.vertex_array_instance_subsequent.start.Value(),
+ regs.vertex_array_instance_subsequent.count.Value(), true);
break;
}
case MAXWELL3D_REG_INDEX(draw_texture.src_y0): {
@@ -84,6 +90,22 @@ void DrawManager::DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 ve
ProcessDraw(false, num_instances);
}
+void DrawManager::DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
+ bool subsequent) {
+ draw_state.topology = topology;
+ draw_state.vertex_buffer.first = vertex_first;
+ draw_state.vertex_buffer.count = vertex_count;
+
+ if (!subsequent) {
+ draw_state.instance_count = 1;
+ }
+
+ draw_state.base_instance = draw_state.instance_count - 1;
+ draw_state.draw_mode = DrawMode::Instance;
+ draw_state.instance_count++;
+ ProcessDraw(false, 1);
+}
+
void DrawManager::DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count,
u32 base_index, u32 base_instance, u32 num_instances) {
const auto& regs{maxwell3d->regs};
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 18d959143..cfc8127fc 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -66,6 +66,8 @@ public:
void DrawArray(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
u32 base_instance, u32 num_instances);
+ void DrawArrayInstanced(PrimitiveTopology topology, u32 vertex_first, u32 vertex_count,
+ bool subsequent);
void DrawIndex(PrimitiveTopology topology, u32 index_first, u32 index_count, u32 base_index,
u32 base_instance, u32 num_instances);
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 9ebece907..34208ed74 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -384,7 +384,7 @@ if (USE_DISCORD_PRESENCE)
discord_impl.cpp
discord_impl.h
)
- target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib)
+ target_link_libraries(yuzu PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 1de093447..d5157c502 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -128,8 +128,8 @@ const std::array<UISettings::Shortcut, 22> Config::default_hotkeys{{
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Fullscreen")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F11"), QStringLiteral("Home+B"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load File")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+O"), QStringLiteral(""), Qt::WidgetWithChildrenShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Load/Remove Amiibo")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F2"), QStringLiteral("Home+A"), Qt::WidgetWithChildrenShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral(""), Qt::WindowShortcut, false}},
- {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral(""), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Restart Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F6"), QStringLiteral("R+Plus+Minus"), Qt::WindowShortcut, false}},
+ {QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Stop Emulation")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("F5"), QStringLiteral("L+Plus+Minus"), Qt::WindowShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Record")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F7"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Reset")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F6"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
{QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "TAS Start/Stop")), QStringLiteral(QT_TRANSLATE_NOOP("Hotkeys", "Main Window")), {QStringLiteral("Ctrl+F5"), QStringLiteral(""), Qt::ApplicationShortcut, false}},
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index e8f9ebfd8..5a48e388b 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -115,17 +115,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]);
- connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
+ connect(player_connected[i], &QCheckBox::clicked, [this, i](int checked) {
// Ensures that the controllers are always connected in sequential order
- if (is_connected) {
- for (std::size_t index = 0; index <= i; ++index) {
- player_connected[index]->setChecked(is_connected);
- }
- } else {
- for (std::size_t index = i; index < player_tabs.size(); ++index) {
- player_connected[index]->setChecked(is_connected);
- }
- }
+ this->propagateMouseClickOnPlayers(i, checked, true);
});
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices);
@@ -183,6 +175,30 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
LoadConfiguration();
}
+void ConfigureInput::propagateMouseClickOnPlayers(size_t player_index, bool checked, bool origin) {
+ // Origin has already been toggled
+ if (!origin) {
+ player_connected[player_index]->setChecked(checked);
+ }
+
+ if (checked) {
+ // Check all previous buttons when checked
+ if (player_index > 0) {
+ propagateMouseClickOnPlayers(player_index - 1, checked, false);
+ }
+ } else {
+ // Unchecked all following buttons when unchecked
+ if (player_index < player_tabs.size() - 1) {
+ // Reconnect current player if it was the last one checked
+ // (player number was reduced by more than one)
+ if (origin && player_connected[player_index + 1]->checkState() == Qt::Checked) {
+ player_connected[player_index]->setCheckState(Qt::Checked);
+ }
+ propagateMouseClickOnPlayers(player_index + 1, checked, false);
+ }
+ }
+}
+
QList<QWidget*> ConfigureInput::GetSubTabs() const {
return {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index c89189c36..abb7f7089 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -56,6 +56,7 @@ private:
void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index);
+ void propagateMouseClickOnPlayers(size_t player_index, bool origin, bool checked);
/// Load configuration settings.
void LoadConfiguration();
diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp
index a4e8af1b4..3fe448f27 100644
--- a/src/yuzu/configuration/shared_translation.cpp
+++ b/src/yuzu/configuration/shared_translation.cpp
@@ -157,6 +157,7 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QWidget* parent) {
INSERT(UISettings, select_user_on_boot, "Prompt for user on game boot", "");
INSERT(UISettings, pause_when_in_background, "Pause emulation when in background", "");
INSERT(UISettings, confirm_before_closing, "Confirm exit while emulation is running", "");
+ INSERT(UISettings, confirm_before_stopping, "Confirm before stopping emulation", "");
INSERT(UISettings, hide_mouse, "Hide mouse on inactivity", "");
INSERT(UISettings, controller_applet_disabled, "Disable controller applet", "");
@@ -383,6 +384,13 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QWidget* parent) {
translations->insert(
{Settings::EnumMetadata<Settings::ConsoleMode>::Index(),
{PAIR(ConsoleMode, Docked, "Docked"), PAIR(ConsoleMode, Handheld, "Handheld")}});
+ translations->insert(
+ {Settings::EnumMetadata<Settings::ConfirmStop>::Index(),
+ {
+ PAIR(ConfirmStop, Ask_Always, "Always ask (Default)"),
+ PAIR(ConfirmStop, Ask_Based_On_Game, "Only if game specifies not to stop"),
+ PAIR(ConfirmStop, Ask_Never, "Never ask"),
+ }});
#undef PAIR
#undef CTX_PAIR
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1a6b63856..1431cf2fe 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -211,7 +211,7 @@ void GMainWindow::ShowTelemetryCallout() {
tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous "
"data is collected</a> to help improve yuzu. "
"<br/><br/>Would you like to share your usage data with us?");
- if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) {
+ if (!question(this, tr("Telemetry"), telemetry_message)) {
Settings::values.enable_telemetry = false;
system->ApplySettings();
}
@@ -2420,9 +2420,8 @@ void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryT
}
}();
- if (QMessageBox::question(this, tr("Remove Entry"), entry_question,
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No) != QMessageBox::Yes) {
+ if (!question(this, tr("Remove Entry"), entry_question, QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No)) {
return;
}
@@ -2521,8 +2520,8 @@ void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget targ
}
}();
- if (QMessageBox::question(this, tr("Remove File"), question, QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No) != QMessageBox::Yes) {
+ if (!GMainWindow::question(this, tr("Remove File"), question,
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::No)) {
return;
}
@@ -3409,10 +3408,13 @@ void GMainWindow::OnRestartGame() {
if (!system->IsPoweredOn()) {
return;
}
- // Make a copy since ShutdownGame edits game_path
- const auto current_game = QString(current_game_path);
- ShutdownGame();
- BootGame(current_game);
+
+ if (ConfirmShutdownGame()) {
+ // Make a copy since ShutdownGame edits game_path
+ const auto current_game = QString(current_game_path);
+ ShutdownGame();
+ BootGame(current_game);
+ }
}
void GMainWindow::OnPauseGame() {
@@ -3434,18 +3436,39 @@ void GMainWindow::OnPauseContinueGame() {
}
void GMainWindow::OnStopGame() {
- if (system->GetExitLocked() && !ConfirmForceLockedExit()) {
- return;
+ if (ConfirmShutdownGame()) {
+ play_time_manager->Stop();
+ // Update game list to show new play time
+ game_list->PopulateAsync(UISettings::values.game_dirs);
+ if (OnShutdownBegin()) {
+ OnShutdownBeginDialog();
+ } else {
+ OnEmulationStopped();
+ }
}
+}
- play_time_manager->Stop();
- // Update game list to show new play time
- game_list->PopulateAsync(UISettings::values.game_dirs);
- if (OnShutdownBegin()) {
- OnShutdownBeginDialog();
+bool GMainWindow::ConfirmShutdownGame() {
+ if (UISettings::values.confirm_before_stopping.GetValue() == ConfirmStop::Ask_Always) {
+ if (system->GetExitLocked()) {
+ if (!ConfirmForceLockedExit()) {
+ return false;
+ }
+ } else {
+ if (!ConfirmChangeGame()) {
+ return false;
+ }
+ }
} else {
- OnEmulationStopped();
+ if (UISettings::values.confirm_before_stopping.GetValue() ==
+ ConfirmStop::Ask_Based_On_Game &&
+ system->GetExitLocked()) {
+ if (!ConfirmForceLockedExit()) {
+ return false;
+ }
+ }
}
+ return true;
}
void GMainWindow::OnLoadComplete() {
@@ -3825,22 +3848,11 @@ void GMainWindow::OnTasRecord() {
const bool is_recording = input_subsystem->GetTas()->Record();
if (!is_recording) {
is_tas_recording_dialog_active = true;
- ControllerNavigation* controller_navigation =
- new ControllerNavigation(system->HIDCore(), this);
- // Use QMessageBox instead of question so we can link controller navigation
- QMessageBox* box_dialog = new QMessageBox();
- box_dialog->setWindowTitle(tr("TAS Recording"));
- box_dialog->setText(tr("Overwrite file of player 1?"));
- box_dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No);
- box_dialog->setDefaultButton(QMessageBox::Yes);
- connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
- [box_dialog](Qt::Key key) {
- QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
- QCoreApplication::postEvent(box_dialog, event);
- });
- int res = box_dialog->exec();
- controller_navigation->UnloadController();
- input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
+
+ bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+ input_subsystem->GetTas()->SaveRecording(answer);
is_tas_recording_dialog_active = false;
}
OnTasStateChanged();
@@ -4081,6 +4093,29 @@ void GMainWindow::OnLoadAmiibo() {
LoadAmiibo(filename);
}
+bool GMainWindow::question(QWidget* parent, const QString& title, const QString& text,
+ QMessageBox::StandardButtons buttons,
+ QMessageBox::StandardButton defaultButton) {
+
+ QMessageBox* box_dialog = new QMessageBox(parent);
+ box_dialog->setWindowTitle(title);
+ box_dialog->setText(text);
+ box_dialog->setStandardButtons(buttons);
+ box_dialog->setDefaultButton(defaultButton);
+
+ ControllerNavigation* controller_navigation =
+ new ControllerNavigation(system->HIDCore(), box_dialog);
+ connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
+ [box_dialog](Qt::Key key) {
+ QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, Qt::NoModifier);
+ QCoreApplication::postEvent(box_dialog, event);
+ });
+ int res = box_dialog->exec();
+
+ controller_navigation->UnloadController();
+ return res == QMessageBox::Yes;
+}
+
void GMainWindow::LoadAmiibo(const QString& filename) {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data");
@@ -4814,8 +4849,7 @@ bool GMainWindow::ConfirmClose() {
return true;
}
const auto text = tr("Are you sure you want to close yuzu?");
- const auto answer = QMessageBox::question(this, tr("yuzu"), text);
- return answer != QMessageBox::No;
+ return question(this, tr("yuzu"), text);
}
void GMainWindow::closeEvent(QCloseEvent* event) {
@@ -4908,11 +4942,11 @@ bool GMainWindow::ConfirmChangeGame() {
if (emu_thread == nullptr)
return true;
- const auto answer = QMessageBox::question(
+ // Use custom question to link controller navigation
+ return question(
this, tr("yuzu"),
tr("Are you sure you want to stop the emulation? Any unsaved progress will be lost."),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- return answer != QMessageBox::No;
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
}
bool GMainWindow::ConfirmForceLockedExit() {
@@ -4922,8 +4956,7 @@ bool GMainWindow::ConfirmForceLockedExit() {
const auto text = tr("The currently running application has requested yuzu to not exit.\n\n"
"Would you like to bypass this and exit anyway?");
- const auto answer = QMessageBox::question(this, tr("yuzu"), text);
- return answer != QMessageBox::No;
+ return question(this, tr("yuzu"), text);
}
void GMainWindow::RequestGameExit() {
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 2346eb3bd..270a40c5f 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -7,6 +7,7 @@
#include <optional>
#include <QMainWindow>
+#include <QMessageBox>
#include <QTimer>
#include <QTranslator>
@@ -15,6 +16,7 @@
#include "input_common/drivers/tas_input.h"
#include "yuzu/compatibility_list.h"
#include "yuzu/hotkeys.h"
+#include "yuzu/util/controller_navigation.h"
#ifdef __unix__
#include <QVariant>
@@ -424,6 +426,11 @@ private:
bool CheckSystemArchiveDecryption();
bool CheckFirmwarePresence();
void ConfigureFilesystemProvider(const std::string& filepath);
+ /**
+ * Open (or not) the right confirm dialog based on current setting and game exit lock
+ * @returns true if the player confirmed or the settings do no require it
+ */
+ bool ConfirmShutdownGame();
QString GetTasStateDescription() const;
bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
@@ -431,6 +438,17 @@ private:
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords);
+ /**
+ * Mimic the behavior of QMessageBox::question but link controller navigation to the dialog
+ * The only difference is that it returns a boolean.
+ *
+ * @returns true if buttons contains QMessageBox::Yes and the user clicks on the "Yes" button.
+ */
+ bool question(QWidget* parent, const QString& title, const QString& text,
+ QMessageBox::StandardButtons buttons =
+ QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
+ QMessageBox::StandardButton defaultButton = QMessageBox::NoButton);
+
std::unique_ptr<Ui::MainWindow> ui;
std::unique_ptr<Core::System> system;
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 975008159..b62ff620c 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -16,7 +16,9 @@
#include "common/settings_enums.h"
using Settings::Category;
+using Settings::ConfirmStop;
using Settings::Setting;
+using Settings::SwitchableSetting;
#ifndef CANNOT_EXPLICITLY_INSTANTIATE
namespace Settings {
@@ -94,6 +96,15 @@ struct Values {
Setting<bool> confirm_before_closing{
linkage, true, "confirmClose", Category::UiGeneral, Settings::Specialization::Default,
true, true};
+
+ SwitchableSetting<ConfirmStop> confirm_before_stopping{linkage,
+ ConfirmStop::Ask_Always,
+ "confirmStop",
+ Category::UiGeneral,
+ Settings::Specialization::Default,
+ true,
+ true};
+
Setting<bool> first_start{linkage, true, "firstStart", Category::Ui};
Setting<bool> pause_when_in_background{linkage,
false,