From 3de8f96373f2ad66d74c29d161c8d9c7460a76ec Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 29 Aug 2025 23:48:25 +0000 Subject: [PATCH 01/20] [gamemode] Make available on other platforms Signed-off-by: lizzie --- CMakeLists.txt | 2 +- externals/gamemode/gamemode_client.h | 28 +++++++++++++++- src/common/CMakeLists.txt | 8 ++--- src/common/gamemode.cpp | 50 ++++++++++++++++++++++++++++ src/common/gamemode.h | 17 ++++++++++ src/common/linux/gamemode.cpp | 40 ---------------------- src/common/linux/gamemode.h | 24 ------------- src/yuzu/main.cpp | 18 +++------- src/yuzu_cmd/yuzu.cpp | 16 ++------- 9 files changed, 105 insertions(+), 98 deletions(-) create mode 100644 src/common/gamemode.cpp create mode 100644 src/common/gamemode.h delete mode 100644 src/common/linux/gamemode.cpp delete mode 100644 src/common/linux/gamemode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f5d7126f92..5e041e3202 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -546,7 +546,7 @@ else() find_package(Catch2 3.0.1 REQUIRED) endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID) + if (PLATFORM_LINUX OR ANDROID) find_package(gamemode 1.7 MODULE) endif() diff --git a/externals/gamemode/gamemode_client.h b/externals/gamemode/gamemode_client.h index b9f64fe460..bfbf61c0c2 100644 --- a/externals/gamemode/gamemode_client.h +++ b/externals/gamemode/gamemode_client.h @@ -1,6 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* -Copyright (c) 2017-2019, Feral Interactive +Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -103,6 +106,7 @@ typedef int (*api_call_pid_return_int)(pid_t); static api_call_return_int REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL; +static api_call_return_int REAL_internal_gamemode_request_restart = NULL; static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; @@ -166,6 +170,10 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) (void **)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, + { "real_gamemode_request_restart", + (void **)&REAL_internal_gamemode_request_restart, + sizeof(REAL_internal_gamemode_request_restart), + false }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), @@ -319,6 +327,24 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void) return REAL_internal_gamemode_query_status(); } +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_restart(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_restart == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_restart missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_restart(); +} + /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) { diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 96ea429e5a..ab983f2def 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -68,6 +68,8 @@ add_library( fs/fs_util.h fs/path_util.cpp fs/path_util.h + gamemode.cpp + gamemode.h hash.h heap_tracker.cpp heap_tracker.h @@ -184,11 +186,7 @@ if(ANDROID) android/applets/software_keyboard.h) endif() -if(LINUX AND NOT APPLE) - target_sources(common PRIVATE linux/gamemode.cpp linux/gamemode.h) - - target_link_libraries(common PRIVATE gamemode::headers) -endif() +target_link_libraries(common PRIVATE gamemode::headers) if(ARCHITECTURE_x86_64) target_sources( diff --git a/src/common/gamemode.cpp b/src/common/gamemode.cpp new file mode 100644 index 0000000000..a3f0ba37ab --- /dev/null +++ b/src/common/gamemode.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// While technically available on al *NIX platforms, Linux is only available +// as the primary target of libgamemode.so - so warnings are suppressed +#ifdef __unix__ +#include +#endif +#include "common/gamemode.h" +#include "common/logging/log.h" +#include "common/settings.h" + +namespace Common::FeralGamemode { + +void Start() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_start() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +void Stop() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_end() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +} // namespace Common::Linux diff --git a/src/common/gamemode.h b/src/common/gamemode.h new file mode 100644 index 0000000000..05b1936bb5 --- /dev/null +++ b/src/common/gamemode.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Common::FeralGamemode { + +/// @brief Start the gamemode client +void Start() noexcept; + +/// @brief Stop the gmemode client +void Stop() noexcept; + +} // namespace Common::FeralGamemode diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp deleted file mode 100644 index 8d3e2934a6..0000000000 --- a/src/common/linux/gamemode.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/linux/gamemode.h" -#include "common/logging/log.h" -#include "common/settings.h" - -namespace Common::Linux { - -void StartGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } -} - -void StopGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } -} - -void SetGamemodeState(bool state) { - if (state) { - StartGamemode(); - } else { - StopGamemode(); - } -} - -} // namespace Common::Linux diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h deleted file mode 100644 index b80646ae27..0000000000 --- a/src/common/linux/gamemode.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace Common::Linux { - -/** - * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StartGamemode(); - -/** - * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StopGamemode(); - -/** - * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - * @param state The new state the gamemode should have - */ -void SetGamemodeState(bool state); - -} // namespace Common::Linux diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d2c12c9d40..7fdf8ed9d9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -22,9 +22,7 @@ #include #include #endif -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" #include @@ -2254,9 +2252,7 @@ void GMainWindow::OnEmulationStopped() { discord_rpc->Update(); -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); // The emulation is stopped, so closing the window or not does not matter anymore disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -3095,10 +3091,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); } void GMainWindow::OnRestartGame() { @@ -3119,10 +3112,7 @@ void GMainWindow::OnPauseGame() { play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); } void GMainWindow::OnPauseContinueGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 4a99f34861..f9f83858e4 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -62,10 +62,7 @@ __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif - -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 @@ -432,10 +429,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); void(system.Run()); if (system.DebuggerEnabled()) { @@ -447,11 +441,7 @@ int main(int argc, char** argv) { system.DetachDebugger(); void(system.Pause()); system.ShutdownMainProcess(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif - + Common::FeralGamemode::Stop(); detached_tasks.WaitForAllTasks(); return 0; } From 49cf701fe928a17cb56d936df3720675f260a4e0 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 00:04:36 +0000 Subject: [PATCH 02/20] [gamemode] make option available on all nixes Signed-off-by: lizzie --- src/yuzu/main.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7fdf8ed9d9..d33b5008e3 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -415,10 +415,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) #ifdef __unix__ SetupSigInterrupts(); #endif - -#ifdef __linux__ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); -#endif UISettings::RestoreWindowState(config); @@ -3407,9 +3404,7 @@ 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(); -#ifdef __linux__ const bool old_gamemode = Settings::values.enable_gamemode.GetValue(); -#endif Settings::SetConfiguringGlobal(true); ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), @@ -3469,11 +3464,9 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); } -#ifdef __linux__ if (Settings::values.enable_gamemode.GetValue() != old_gamemode) { SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); } -#endif if (!multiplayer_state->IsHostingPublicRoom()) { multiplayer_state->UpdateCredentials(); @@ -4755,13 +4748,15 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } -#ifdef __linux__ void GMainWindow::SetGamemodeEnabled(bool state) { if (emulation_running) { - Common::Linux::SetGamemodeState(state); + if (state) { + Common::FeralGamemode::Start(); + } else { + Common::FeralGamemode::Stop(); + } } } -#endif void GMainWindow::changeEvent(QEvent* event) { #ifdef __unix__ @@ -4920,7 +4915,9 @@ int main(int argc, char* argv[]) { // the user folder in the Qt Frontend, we need to cd into that working directory const auto bin_path = Common::FS::GetBundleDirectory() / ".."; chdir(Common::FS::PathToUTF8String(bin_path).c_str()); -#elif defined(__unix__) && !defined(__ANDROID__) +#endif + +#ifdef __unix__ // Set the DISPLAY variable in order to open web browsers // TODO (lat9nq): Find a better solution for AppImages to start external applications if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) { From c49501ec7eb3acc739337beed51bd2e7f09ac7fa Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 08:43:59 +0000 Subject: [PATCH 03/20] [gamemode] extra win/lin/macos fixes Signed-off-by: lizzie --- externals/CMakeLists.txt | 2 +- src/yuzu/main.cpp | 2 +- src/yuzu_cmd/yuzu.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 754ba61a0b..4c11104de2 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -140,7 +140,7 @@ if (ANDROID AND ARCHITECTURE_arm64) AddJsonPackage(libadrenotools) endif() -if (UNIX AND NOT APPLE AND NOT TARGET gamemode::headers) +if (NOT TARGET gamemode::headers) add_library(gamemode INTERFACE) target_include_directories(gamemode INTERFACE gamemode) add_library(gamemode::headers ALIAS gamemode) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d33b5008e3..4ab578067d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3088,7 +3088,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); } void GMainWindow::OnRestartGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index f9f83858e4..8e46f36aec 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -429,7 +429,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); void(system.Run()); if (system.DebuggerEnabled()) { From 125a7dacb7673017c14577ca23d9ce0c52c881c1 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 13:40:57 +0000 Subject: [PATCH 04/20] [gamemode] windows fix Signed-off-by: lizzie --- src/yuzu/main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e3922759b0..c6980968c8 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -315,8 +315,8 @@ private: void SetupSigInterrupts(); static void HandleSigInterrupt(int); void OnSigInterruptNotifierActivated(); - void SetGamemodeEnabled(bool state); #endif + void SetGamemodeEnabled(bool state); Service::AM::FrontendAppletParameters ApplicationAppletParameters(); Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, From 178d20b780d6c55ab4d3f14cc8717cfa52b9a0c7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 13 Sep 2025 20:48:40 +0000 Subject: [PATCH 05/20] [gamemode] default disable on msvc, move to UI category Signed-off-by: lizzie --- src/common/settings.h | 10 ++++++++-- src/common/settings_common.h | 1 - src/qt_common/shared_translation.cpp | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index 891bde608c..3b301ccda4 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -616,8 +616,14 @@ struct Values { true, true}; - // Linux - SwitchableSetting enable_gamemode{linkage, true, "enable_gamemode", Category::Linux}; + // Linux/MinGW may support (requires libdl support) + SwitchableSetting enable_gamemode{linkage, +#ifndef _MSC_VER + true, +#else + false, +#endif + "enable_gamemode", Category::UiGeneral}; // Controls InputSetting> players; diff --git a/src/common/settings_common.h b/src/common/settings_common.h index af16ec692b..7902cbf945 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -44,7 +44,6 @@ enum class Category : u32 { Multiplayer, Services, Paths, - Linux, LibraryApplet, MaxEnum, }; diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index 98a55e1fcf..230321e722 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -445,7 +445,10 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Whether or not to check for updates upon startup.")); // Linux - INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString()); + INSERT(UISettings, + enable_gamemode, + tr("Enable Gamemode"), + QString()); // Ui Debugging From 67d537f7b4687b23b4148222c239d65c1aeb4863 Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 26 Sep 2025 19:30:17 -0400 Subject: [PATCH 06/20] [cmake] vendor gamemode Signed-off-by: crueter --- CMakeLists.txt | 6 +- externals/CMakeLists.txt | 6 +- externals/cpmfile.json | 7 + externals/gamemode/gamemode_client.h | 402 --------------------------- 4 files changed, 12 insertions(+), 409 deletions(-) delete mode 100644 externals/gamemode/gamemode_client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e041e3202..024badc275 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,7 +320,6 @@ endif() if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX)) set(HAS_NCE 1) add_compile_definitions(HAS_NCE=1) - find_package(oaknut 2.0.1) endif() if (YUZU_ROOM) @@ -546,10 +545,6 @@ else() find_package(Catch2 3.0.1 REQUIRED) endif() - if (PLATFORM_LINUX OR ANDROID) - find_package(gamemode 1.7 MODULE) - endif() - if (ENABLE_OPENSSL) find_package(OpenSSL 1.1.1 REQUIRED) endif() @@ -673,6 +668,7 @@ add_subdirectory(externals) # pass targets from externals find_package(libusb) find_package(VulkanMemoryAllocator) +find_package(gamemode) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) find_package(xbyak) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4c11104de2..2d0d5a7401 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -140,9 +140,11 @@ if (ANDROID AND ARCHITECTURE_arm64) AddJsonPackage(libadrenotools) endif() -if (NOT TARGET gamemode::headers) +AddJsonPackage(gamemode) + +if (gamemode_ADDED) add_library(gamemode INTERFACE) - target_include_directories(gamemode INTERFACE gamemode) + target_include_directories(gamemode INTERFACE ${gamemode_SOURCE_DIR}) add_library(gamemode::headers ALIAS gamemode) endif() diff --git a/externals/cpmfile.json b/externals/cpmfile.json index dcafc8f97d..6758957550 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -70,5 +70,12 @@ "sha": "73f3cbb237", "hash": "c08c03063938339d61392b687562909c1a92615b6ef39ec8df19ea472aa6b6478e70d7d5e33d4a27b5d23f7806daf57fe1bacb8124c8a945c918c7663a9e8532", "find_args": "CONFIG" + }, + "gamemode": { + "repo": "FeralInteractive/gamemode", + "sha": "ce6fe122f3", + "hash": "ff62f3c8638528e7afb7336f7c8f9940453d2871446c36f639e3114cc8b5c7f9a817ed209c45e0e061793fc3ee9ba35a4b05140d39f5175c2aacfa0703fddfc4", + "version": "1.7", + "find_args": "MODULE" } } diff --git a/externals/gamemode/gamemode_client.h b/externals/gamemode/gamemode_client.h deleted file mode 100644 index bfbf61c0c2..0000000000 --- a/externals/gamemode/gamemode_client.h +++ /dev/null @@ -1,402 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -/* - -Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Feral Interactive nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - */ -#ifndef CLIENT_GAMEMODE_H -#define CLIENT_GAMEMODE_H -/* - * GameMode supports the following client functions - * Requests are refcounted in the daemon - * - * int gamemode_request_start() - Request gamemode starts - * 0 if the request was sent successfully - * -1 if the request failed - * - * int gamemode_request_end() - Request gamemode ends - * 0 if the request was sent successfully - * -1 if the request failed - * - * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and - * destruction, as appropriate. In this configuration, errors will be printed to stderr - * - * int gamemode_query_status() - Query the current status of gamemode - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * const char* gamemode_error_string() - Get an error string - * returns a string describing any of the above errors - * - * Note: All the above requests can be blocking - dbus requests can and will block while the daemon - * handles the request. It is not recommended to make these calls in performance critical code - */ - -#include -#include - -#include -#include - -#include - -#include - -static char internal_gamemode_client_error_string[512] = { 0 }; - -/** - * Load libgamemode dynamically to dislodge us from most dependencies. - * This allows clients to link and/or use this regardless of runtime. - * See SDL2 for an example of the reasoning behind this in terms of - * dynamic versioning as well. - */ -static volatile int internal_libgamemode_loaded = 1; - -/* Typedefs for the functions to load */ -typedef int (*api_call_return_int)(void); -typedef const char *(*api_call_return_cstring)(void); -typedef int (*api_call_pid_return_int)(pid_t); - -/* Storage for functors */ -static api_call_return_int REAL_internal_gamemode_request_start = NULL; -static api_call_return_int REAL_internal_gamemode_request_end = NULL; -static api_call_return_int REAL_internal_gamemode_query_status = NULL; -static api_call_return_int REAL_internal_gamemode_request_restart = NULL; -static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; - -/** - * Internal helper to perform the symbol binding safely. - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol( - void *handle, const char *name, void **out_func, size_t func_size, bool required) -{ - void *symbol_lookup = NULL; - char *dl_error = NULL; - - /* Safely look up the symbol */ - symbol_lookup = dlsym(handle, name); - dl_error = dlerror(); - if (required && (dl_error || !symbol_lookup)) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "dlsym failed - %s", - dl_error); - return -1; - } - - /* Have the symbol correctly, copy it to make it usable */ - memcpy(out_func, &symbol_lookup, func_size); - return 0; -} - -/** - * Loads libgamemode and needed functions - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_load_libgamemode(void) -{ - /* We start at 1, 0 is a success and -1 is a fail */ - if (internal_libgamemode_loaded != 1) { - return internal_libgamemode_loaded; - } - - /* Anonymous struct type to define our bindings */ - struct binding { - const char *name; - void **functor; - size_t func_size; - bool required; - } bindings[] = { - { "real_gamemode_request_start", - (void **)&REAL_internal_gamemode_request_start, - sizeof(REAL_internal_gamemode_request_start), - true }, - { "real_gamemode_request_end", - (void **)&REAL_internal_gamemode_request_end, - sizeof(REAL_internal_gamemode_request_end), - true }, - { "real_gamemode_query_status", - (void **)&REAL_internal_gamemode_query_status, - sizeof(REAL_internal_gamemode_query_status), - false }, - { "real_gamemode_request_restart", - (void **)&REAL_internal_gamemode_request_restart, - sizeof(REAL_internal_gamemode_request_restart), - false }, - { "real_gamemode_error_string", - (void **)&REAL_internal_gamemode_error_string, - sizeof(REAL_internal_gamemode_error_string), - true }, - { "real_gamemode_request_start_for", - (void **)&REAL_internal_gamemode_request_start_for, - sizeof(REAL_internal_gamemode_request_start_for), - false }, - { "real_gamemode_request_end_for", - (void **)&REAL_internal_gamemode_request_end_for, - sizeof(REAL_internal_gamemode_request_end_for), - false }, - { "real_gamemode_query_status_for", - (void **)&REAL_internal_gamemode_query_status_for, - sizeof(REAL_internal_gamemode_query_status_for), - false }, - }; - - void *libgamemode = NULL; - - /* Try and load libgamemode */ - libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); - if (!libgamemode) { - /* Attempt to load unversioned library for compatibility with older - * versions (as of writing, there are no ABI changes between the two - - * this may need to change if ever ABI-breaking changes are made) */ - libgamemode = dlopen("libgamemode.so", RTLD_NOW); - if (!libgamemode) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "dlopen failed - %s", - dlerror()); - internal_libgamemode_loaded = -1; - return -1; - } - } - - /* Attempt to bind all symbols */ - for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { - struct binding *binder = &bindings[i]; - - if (internal_bind_libgamemode_symbol(libgamemode, - binder->name, - binder->functor, - binder->func_size, - binder->required)) { - internal_libgamemode_loaded = -1; - return -1; - }; - } - - /* Success */ - internal_libgamemode_loaded = 0; - return 0; -} - -/** - * Redirect to the real libgamemode - */ -__attribute__((always_inline)) static inline const char *gamemode_error_string(void) -{ - /* If we fail to load the system gamemode, or we have an error string already, return our error - * string instead of diverting to the system version */ - if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { - return internal_gamemode_client_error_string; - } - - /* Assert for static analyser that the function is not NULL */ - assert(REAL_internal_gamemode_error_string != NULL); - - return REAL_internal_gamemode_error_string(); -} - -/** - * Redirect to the real libgamemode - * Allow automatically requesting game mode - * Also prints errors as they happen. - */ -#ifdef GAMEMODE_AUTO -__attribute__((constructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_start(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - /* Assert for static analyser that the function is not NULL */ - assert(REAL_internal_gamemode_request_start != NULL); - - if (REAL_internal_gamemode_request_start() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -#ifdef GAMEMODE_AUTO -__attribute__((destructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_end(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - /* Assert for static analyser that the function is not NULL */ - assert(REAL_internal_gamemode_request_end != NULL); - - if (REAL_internal_gamemode_request_end() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_query_status missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status(); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_restart(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_restart == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_request_restart missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_restart(); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_start_for == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_request_start_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_start_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_end_for == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_request_end_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_end_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status_for == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_query_status_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status_for(pid); -} - -#endif // CLIENT_GAMEMODE_H From eac3addacc699c6ede63e86a2c590d8a0e039af2 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 29 Sep 2025 07:46:59 +0000 Subject: [PATCH 07/20] fix licsense Signed-off-by: lizzie --- src/common/settings_common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 7902cbf945..0107895c8c 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later From 85b5e650cc01a97956b3e4cc268f691a8fbc4cba Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 29 Sep 2025 19:41:01 +0200 Subject: [PATCH 08/20] [dist, docs] Clearer wording for settings, guidelines for new settings (#2570) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2570 Reviewed-by: MaranBr Reviewed-by: crueter Co-authored-by: lizzie Co-committed-by: lizzie --- docs/Development.md | 22 +++ .../app/src/main/res/values/arrays.xml | 4 + .../app/src/main/res/values/strings.xml | 14 +- src/qt_common/shared_translation.cpp | 144 ++++++++---------- .../configure_profile_manager.cpp | 4 +- 5 files changed, 100 insertions(+), 88 deletions(-) diff --git a/docs/Development.md b/docs/Development.md index 1ed75a7c23..c223409243 100644 --- a/docs/Development.md +++ b/docs/Development.md @@ -36,6 +36,28 @@ Pull requests are only to be merged by core developers when properly tested and - Maintainers are permitted to change namespaces at will. - Commits within PRs are not required to be namespaced, but it is highly recommended. +## Adding new settings + +When adding new settings, use `tr("Setting:")` if the setting is meant to be a field, otherwise use `tr("Setting")` if the setting is meant to be a Yes/No or checkmark type of setting, see [this short style guide](https://learn.microsoft.com/en-us/style-guide/punctuation/colons#in-ui). + +- The majority of software must work with the default option selected for such setting. Unless the setting significantly degrades performance. +- Debug settings must never be turned on by default. +- Provide reasonable bounds (for example, a setting controlling the amount of VRAM should never be 0). +- The description of the setting must be short and concise, if the setting "does a lot of things" consider splitting the setting into multiple if possible. +- Try to avoid excessive/redundant explainations "recommended for most users and games" can just be "(recommended)". +- Try to not write "slow/fast" options unless it clearly degrades/increases performance for a given case, as most options may modify behaviour that result in different metrics accross different systems. If for example the option is an "accuracy" option, writing "High" is sufficient to imply "Slow". No need to write "High (Slow)". + +Some examples: +- "[...] negatively affecting image quality", "[...] degrading image quality": Same wording but with less filler. +- "[...] this may cause some glitches or crashes in some games", "[...] this may cause soft-crashes": Crashes implies there may be glitches (as crashes are technically a form of a fatal glitch). The entire sentence is structured as "may cause [...] on some games", which is redundant, because "may cause [...] in games" has the same semantic meaning ("may" is a chance that it will occur on "some" given set). +- "FIFO Relaxed is similar to FIFO [...]", "FIFO Relaxed [...]": The name already implies similarity. +- "[...] but may also reduce performance in some cases", "[...] but may degrade performance": Again, "some cases" and "may" implies there is a probability. +- "[...] it can [...] in some cases", "[...] it can [...]": Implied probability. + +Before adding a new setting, consider: +- Does the piece of code that the setting pertains to, make a significant difference if it's on/off? +- Can it be auto-detected? + # IDE setup ## VSCode diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 08ca53ad81..1b66c191d3 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -251,7 +251,9 @@ @string/scaling_filter_nearest_neighbor @string/scaling_filter_bilinear @string/scaling_filter_bicubic + @string/scaling_filter_spline1 @string/scaling_filter_gaussian + @string/scaling_filter_lanczos @string/scaling_filter_scale_force @string/scaling_filter_fsr @string/scaling_filter_area @@ -265,6 +267,8 @@ 4 5 6 + 7 + 8 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 48ea3ed99b..c05420a17f 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -865,12 +865,12 @@ Abort Continue System Archive Not Found - %s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs. + %s is missing. Please dump your system archives.\nContinuing emulation may result in crashes. A system archive Save/Load Error Fatal Error - A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs. - Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled. + A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes. + Turning off this setting will significantly degrade performance. It's recommended that you leave this setting enabled. Device RAM: %1$s\nRecommended: %2$s %1$s %2$s No bootable game present! @@ -941,12 +941,12 @@ Normal High - Extreme (Slow) + Extreme Default - Unsafe (fast) - Safe (stable) + Unsafe + Safe ASTC Decoding Method @@ -992,7 +992,9 @@ Nearest Neighbor Bilinear Bicubic + Spline-1 Gaussian + Lanczos ScaleForce AMD FidelityFX™ Super Resolution Area diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index 98a55e1fcf..8f5d929b74 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -62,22 +62,20 @@ std::unique_ptr InitializeTranslations(QObject* parent) Settings, use_multi_core, tr("Multicore CPU Emulation"), - tr("This option increases CPU emulation thread use from 1 to the Switch’s maximum of 4.\n" - "This is mainly a debug option and shouldn’t be disabled.")); + tr("This option increases CPU emulation thread use from 1 to the maximum of 4.\n" + "This is mainly a debug option and shouldn't be disabled.")); INSERT( Settings, memory_layout_mode, tr("Memory Layout"), - tr("Increases the amount of emulated RAM from the stock 4GB of the retail Switch to the " - "developer kit's 8/6GB.\nIt’s doesn’t improve stability or performance and is intended " - "to let big texture mods fit in emulated RAM.\nEnabling it will increase memory " - "use. It is not recommended to enable unless a specific game with a texture mod needs " - "it.")); + tr("Increases the amount of emulated RAM from 4GB of the board to the " + "devkit 8/6GB.\nDoesn't affect performance/stability but may allow HD texture " + "mods to load.")); INSERT(Settings, use_speed_limit, QString(), QString()); INSERT(Settings, speed_limit, tr("Limit Speed Percent"), - tr("Controls the game's maximum rendering speed, but it’s up to each game if it runs " + tr("Controls the game's maximum rendering speed, but it's up to each game if it runs " "faster or not.\n200% for a 30 FPS game is 60 FPS, and for a " "60 FPS game it will be 120 FPS.\nDisabling it means unlocking the framerate to the " "maximum your PC can reach.")); @@ -86,15 +84,13 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Synchronize Core Speed"), tr("Synchronizes CPU core speed with the game's maximum rendering speed to boost FPS " "without affecting game speed (animations, physics, etc.).\n" - "Compatibility varies by game; many (especially older ones) may not respond well.\n" "Can help reduce stuttering at lower framerates.")); // Cpu INSERT(Settings, cpu_accuracy, tr("Accuracy:"), - tr("This setting controls the accuracy of the emulated CPU.\nDon't change this unless " - "you know what you are doing.")); + tr("Change the accuracy of the emulated CPU (for debugging only).")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); INSERT(Settings, use_fast_cpu_time, QString(), QString()); @@ -110,7 +106,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) cpu_ticks, tr("Custom CPU Ticks"), tr("Set a custom value of CPU ticks. Higher values can increase performance, but may " - "also cause the game to freeze. A range of 77–21000 is recommended.")); + "cause deadlocks. A range of 77-21000 is recommended.")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); // Cpu Debug @@ -144,8 +140,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) cpuopt_unsafe_fastmem_check, tr("Disable address space checks"), tr("This option improves speed by eliminating a safety check before every memory " - "read/write in guest.\nDisabling it may allow a game to read/write the emulator's " - "memory.")); + "operation.\nDisabling it may allow arbitrary code execution.")); INSERT( Settings, cpuopt_unsafe_ignore_global_monitor, @@ -159,36 +154,31 @@ std::unique_ptr InitializeTranslations(QObject* parent) Settings, renderer_backend, tr("API:"), - tr("Switches between the available graphics APIs.\nVulkan is recommended in most cases.")); + tr("Changes the output graphics API.\nVulkan is recommended.")); INSERT(Settings, vulkan_device, tr("Device:"), - tr("This setting selects the GPU to use with the Vulkan backend.")); + tr("This setting selects the GPU to use (Vulkan only).")); INSERT(Settings, shader_backend, tr("Shader Backend:"), - tr("The shader backend to use for the OpenGL renderer.\nGLSL is the fastest in " - "performance and the best in rendering accuracy.\n" - "GLASM is a deprecated NVIDIA-only backend that offers much better shader building " - "performance at the cost of FPS and rendering accuracy.\n" - "SPIR-V compiles the fastest, but yields poor results on most GPU drivers.")); + tr("The shader backend to use with OpenGL.\nGLSL is recommended.")); INSERT(Settings, resolution_setup, tr("Resolution:"), - tr("Forces the game to render at a different resolution.\nHigher resolutions require " - "much more VRAM and bandwidth.\n" - "Options lower than 1X can cause rendering issues.")); + tr("Forces to render at a different resolution.\n" + "Higher resolutions require more VRAM and bandwidth.\n" + "Options lower than 1X can cause artifacts.")); INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), - tr("Determines how sharpened the image will look while using FSR’s dynamic contrast.")); + tr("Determines how sharpened the image will look using FSR's dynamic contrast.")); INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), - tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA has a " - "lower performance impact and can produce a better and more stable picture under " - "very low resolutions.")); + tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA " + "can produce a more stable picture in lower resolutions.")); INSERT(Settings, fullscreen_mode, tr("Fullscreen Mode:"), @@ -199,18 +189,17 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, aspect_ratio, tr("Aspect Ratio:"), - tr("Stretches the game to fit the specified aspect ratio.\nSwitch games only support " - "16:9, so custom game mods are required to get other ratios.\nAlso controls the " + tr("Stretches the renderer to fit the specified aspect ratio.\nMost games only support " + "16:9, so modifications are required to get other ratios.\nAlso controls the " "aspect ratio of captured screenshots.")); INSERT(Settings, use_disk_shader_cache, - tr("Use disk pipeline cache"), + tr("Use persistent pipeline cache"), tr("Allows saving shaders to storage for faster loading on following game " - "boots.\nDisabling " - "it is only intended for debugging.")); + "boots.\nDisabling it is only intended for debugging.")); INSERT(Settings, optimize_spirv_output, - tr("Optimize SPIRV output shader"), + tr("Optimize SPIRV output"), tr("Runs an additional optimization pass over generated SPIRV shaders.\n" "Will increase time required for shader compilation.\nMay slightly improve " "performance.\nThis feature is experimental.")); @@ -229,37 +218,35 @@ std::unique_ptr InitializeTranslations(QObject* parent) accelerate_astc, tr("ASTC Decoding Method:"), tr("This option controls how ASTC textures should be decoded.\n" - "CPU: Use the CPU for decoding, slowest but safest method.\n" - "GPU: Use the GPU's compute shaders to decode ASTC textures, recommended for most " - "games and users.\n" - "CPU Asynchronously: Use the CPU to decode ASTC textures as they arrive. Completely " - "eliminates ASTC decoding\nstuttering at the cost of rendering issues while the " - "texture is being decoded.")); + "CPU: Use the CPU for decoding.\n" + "GPU: Use the GPU's compute shaders to decode ASTC textures (recommended).\n" + "CPU Asynchronously: Use the CPU to decode ASTC textures on demand. Eliminates" + "ASTC decoding\nstuttering but may present artifacts.")); INSERT( Settings, astc_recompression, tr("ASTC Recompression Method:"), - tr("Almost all desktop and laptop dedicated GPUs lack support for ASTC textures, forcing " - "the emulator to decompress to an intermediate format any card supports, RGBA8.\n" - "This option recompresses RGBA8 to either the BC1 or BC3 format, saving VRAM but " - "negatively affecting image quality.")); + tr("Most GPUs lack support for ASTC textures and must decompress to an" + "intermediate format: RGBA8.\n" + "BC1/BC3: The intermediate format will be recompressed to BC1 or BC3 format,\n" + " saving VRAM but degrading image quality.")); INSERT(Settings, vram_usage_mode, tr("VRAM Usage Mode:"), - tr("Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance.\nAggressive mode may severely impact the performance of other applications such as recording software.")); + tr("Selects whether the emulator should prefer to conserve memory or make maximum usage of available video memory for performance.\nAggressive mode may impact performance of other applications such as recording software.")); INSERT(Settings, skip_cpu_inner_invalidation, tr("Skip CPU Inner Invalidation"), - tr("Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and " - "improving it's performance. This may cause glitches or crashes on some games.")); + tr("Skips certain cache invalidations during memory updates, reducing CPU usage and " + "improving latency. This may cause soft-crashes.")); INSERT( Settings, vsync_mode, tr("VSync Mode:"), tr("FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen " - "refresh rate.\nFIFO Relaxed is similar to FIFO but allows tearing as it recovers from " - "a slow down.\nMailbox can have lower latency than FIFO and does not tear but may drop " - "frames.\nImmediate (no synchronization) just presents whatever is available and can " + "refresh rate.\nFIFO Relaxed allows tearing as it recovers from a slow down.\n" + "Mailbox can have lower latency than FIFO and does not tear but may drop " + "frames.\nImmediate (no synchronization) presents whatever is available and can " "exhibit tearing.")); INSERT(Settings, bg_red, QString(), QString()); INSERT(Settings, bg_green, QString(), QString()); @@ -267,7 +254,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) // Renderer (Advanced Graphics) INSERT(Settings, sync_memory_operations, tr("Sync Memory Operations"), - tr("Ensures data consistency between compute and memory operations.\nThis option should fix issues in some games, but may also reduce performance in some cases.\nUnreal Engine 4 games often see the most significant changes thereof.")); + tr("Ensures data consistency between compute and memory operations.\nThis option fixes issues in games, but may degrade performance.\nUnreal Engine 4 games often see the most significant changes thereof.")); INSERT(Settings, async_presentation, tr("Enable asynchronous presentation (Vulkan only)"), @@ -281,8 +268,7 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, max_anisotropy, tr("Anisotropic Filtering:"), - tr("Controls the quality of texture rendering at oblique angles.\nIt’s a light setting " - "and safe to set at 16x on most GPUs.")); + tr("Controls the quality of texture rendering at oblique angles.\nSafe to set at 16x on most GPUs.")); INSERT(Settings, gpu_accuracy, tr("GPU Accuracy:"), @@ -292,11 +278,11 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, dma_accuracy, tr("DMA Accuracy:"), - tr("Controls the DMA precision accuracy. Safe precision can fix issues in some games, but it can also impact performance in some cases.\nIf unsure, leave this on Default.")); + tr("Controls the DMA precision accuracy. Safe precision fixes issues in some games but may degrade performance.")); INSERT(Settings, use_asynchronous_shaders, - tr("Use asynchronous shader building (Hack)"), - tr("Enables asynchronous shader compilation, which may reduce shader stutter.")); + tr("Enable asynchronous shader compilation (Hack)"), + tr("May reduce shader stutter.")); INSERT(Settings, use_fast_gpu_time, QString(), QString()); INSERT(Settings, fast_gpu_time, @@ -314,8 +300,8 @@ std::unique_ptr InitializeTranslations(QObject* parent) Settings, enable_compute_pipelines, tr("Enable Compute Pipelines (Intel Vulkan Only)"), - tr("Enable compute pipelines, required by some games.\nThis setting only exists for Intel " - "proprietary drivers, and may crash if enabled.\nCompute pipelines are always enabled " + tr("Required by some games.\nThis setting only exists for Intel " + "proprietary drivers and may crash if enabled.\nCompute pipelines are always enabled " "on all other drivers.")); INSERT( Settings, @@ -343,12 +329,12 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, dyna_state, tr("Extended Dynamic State"), - tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues with some drivers and vendors.\nThe default value may vary depending on your system and hardware capabilities.\nThis value can be changed until stability and a better visual quality are achieved.")); + tr("Controls the number of features that can be used in Extended Dynamic State.\nHigher numbers allow for more features and can increase performance, but may cause issues.\nThe default value is per-system.")); INSERT(Settings, provoking_vertex, tr("Provoking Vertex"), - tr("Improves lighting and vertex handling in certain games.\n" + tr("Improves lighting and vertex handling in some games.\n" "Only Vulkan 1.0+ devices support this extension.")); INSERT(Settings, @@ -363,8 +349,8 @@ std::unique_ptr InitializeTranslations(QObject* parent) sample_shading_fraction, tr("Sample Shading"), tr("Allows the fragment shader to execute per sample in a multi-sampled fragment " - "instead once per fragment. Improves graphics quality at the cost of some performance.\n" - "Higher values improve quality more but also reduce performance to a greater extent.")); + "instead of once per fragment. Improves graphics quality at the cost of performance.\n" + "Higher values improve quality but degrade performance.")); // Renderer (Debug) @@ -372,31 +358,30 @@ std::unique_ptr InitializeTranslations(QObject* parent) INSERT(Settings, rng_seed, tr("RNG Seed"), - tr("Controls the seed of the random number generator.\nMainly used for speedrunning " - "purposes.")); + tr("Controls the seed of the random number generator.\nMainly used for speedrunning.")); INSERT(Settings, rng_seed_enabled, QString(), QString()); - INSERT(Settings, device_name, tr("Device Name"), tr("The name of the emulated Switch.")); + INSERT(Settings, device_name, tr("Device Name"), tr("The name of the console.")); INSERT(Settings, custom_rtc, tr("Custom RTC Date:"), - tr("This option allows to change the emulated clock of the Switch.\n" + tr("This option allows to change the clock of the console.\n" "Can be used to manipulate time in games.")); INSERT(Settings, custom_rtc_enabled, QString(), QString()); INSERT(Settings, custom_rtc_offset, QStringLiteral(" "), - QStringLiteral("The number of seconds from the current unix time")); + tr("The number of seconds from the current unix time")); INSERT(Settings, language_index, tr("Language:"), - tr("Note: this can be overridden when region setting is auto-select")); - INSERT(Settings, region_index, tr("Region:"), tr("The region of the emulated Switch.")); - INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the emulated Switch.")); + tr("This option can be overridden when region setting is auto-select")); + INSERT(Settings, region_index, tr("Region:"), tr("The region of the console.")); + INSERT(Settings, time_zone_index, tr("Time Zone:"), tr("The time zone of the console.")); INSERT(Settings, sound_index, tr("Sound Output Mode:"), QString()); INSERT(Settings, use_docked_mode, tr("Console Mode:"), - tr("Selects if the console is emulated in Docked or Handheld mode.\nGames will change " + tr("Selects if the console is in Docked or Handheld mode.\nGames will change " "their resolution, details and supported controllers and depending on this setting.\n" "Setting to Handheld can help improve performance for low end systems.")); INSERT(Settings, current_user, QString(), QString()); @@ -418,27 +403,26 @@ std::unique_ptr InitializeTranslations(QObject* parent) // Ui General INSERT(UISettings, select_user_on_boot, - tr("Prompt for user on game boot"), - tr("Ask to select a user profile on each boot, useful if multiple people use Eden on " - "the same PC.")); + tr("Prompt for user profile on boot"), + tr("Useful if multiple people use the same PC.")); INSERT(UISettings, pause_when_in_background, - tr("Pause emulation when in background"), - tr("This setting pauses Eden when focusing other windows.")); + tr("Pause when not in focus"), + tr("Pauses emulation when focusing on other windows.")); INSERT(UISettings, confirm_before_stopping, tr("Confirm before stopping emulation"), - tr("This setting overrides game prompts asking to confirm stopping the game.\nEnabling " + tr("Overrides prompts asking to confirm stopping the emulation.\nEnabling " "it bypasses such prompts and directly exits the emulation.")); INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), - tr("This setting hides the mouse after 2.5s of inactivity.")); + tr("Hides the mouse after 2.5s of inactivity.")); INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), - tr("Forcibly disables the use of the controller applet by guests.\nWhen a guest " - "attempts to open the controller applet, it is immediately closed.")); + tr("Forcibly disables the use of the controller applet in emulated programs.\n" + "When a program attempts to open the controller applet, it is immediately closed.")); INSERT(UISettings, check_for_updates, tr("Check for updates"), diff --git a/src/yuzu/configuration/configure_profile_manager.cpp b/src/yuzu/configuration/configure_profile_manager.cpp index f5de08a676..df74738df4 100644 --- a/src/yuzu/configuration/configure_profile_manager.cpp +++ b/src/yuzu/configuration/configure_profile_manager.cpp @@ -373,13 +373,13 @@ bool ConfigureProfileManager::LoadAvatarData() { const auto romfs = nca->GetRomFS(); if (!romfs) { QMessageBox::warning(this, tr("Error loading archive"), - tr("Archive does not contain romfs. It is probably corrupt.")); + tr("Could not locate RomFS. Your file or decryption keys may be corrupted.")); return false; } const auto extracted = FileSys::ExtractRomFS(romfs); if (!extracted) { QMessageBox::warning(this, tr("Error extracting archive"), - tr("Archive could not be extracted. It is probably corrupt.")); + tr("Could not extract RomFS. Your file or decryption keys may be corrupted.")); return false; } const auto chara_dir = extracted->GetSubdirectory("chara"); From 824dc6948e5dca6cfeb0a42eb98cae8ed57a58d3 Mon Sep 17 00:00:00 2001 From: nyx Date: Mon, 29 Sep 2025 22:38:26 +0200 Subject: [PATCH 09/20] [android] input over(lay)haul 2: Individual scaling of buttons (#2562) ### (Needs testing) This PR makes it possible to adjust the scale of each touch input overlay button independently from the global scale This individual value always goes on top of the global scale. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2562 Co-authored-by: nyx Co-committed-by: nyx --- .../yuzu_emu/fragments/EmulationFragment.kt | 2 + .../org/yuzu/yuzu_emu/overlay/InputOverlay.kt | 255 +++++++++++++++--- .../overlay/InputOverlayDrawableDpad.kt | 6 +- .../overlay/InputOverlayDrawableJoystick.kt | 6 +- .../yuzu_emu/overlay/OverlayScaleDialog.kt | 124 +++++++++ .../yuzu_emu/overlay/model/OverlayControl.kt | 61 +++-- .../overlay/model/OverlayControlData.kt | 7 +- .../yuzu_emu/utils/DirectoryInitialization.kt | 7 +- .../app/src/main/jni/android_config.cpp | 6 +- .../app/src/main/jni/android_settings.h | 1 + .../app/src/main/jni/native_config.cpp | 13 +- .../main/res/layout/dialog_overlay_scale.xml | 74 +++++ .../app/src/main/res/values/strings.xml | 1 + src/common/android/id_cache.cpp | 14 +- src/common/android/id_cache.h | 6 +- 15 files changed, 506 insertions(+), 77 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt create mode 100644 src/android/app/src/main/res/layout/dialog_overlay_scale.xml diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 8162098caa..4487812ad1 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -924,6 +924,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { IntSetting.OVERLAY_OPACITY.reset() binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement() + binding.surfaceInputOverlay.resetIndividualControlScale() } } @@ -1546,6 +1547,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { .setNeutralButton(R.string.slider_default) { _: DialogInterface?, _: Int -> setControlScale(50) setControlOpacity(100) + binding.surfaceInputOverlay.resetIndividualControlScale() } .show() } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt index 737e035840..9f050a5053 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.overlay @@ -13,6 +13,8 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.VectorDrawable import android.os.Build +import android.os.Handler +import android.os.Looper import android.util.AttributeSet import android.view.HapticFeedbackConstants import android.view.MotionEvent @@ -52,6 +54,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : private var dpadBeingConfigured: InputOverlayDrawableDpad? = null private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null + private var scaleDialog: OverlayScaleDialog? = null + private var touchStartX = 0f + private var touchStartY = 0f + private var hasMoved = false + private val moveThreshold = 20f + private lateinit var windowInsets: WindowInsets var layout = OverlayLayout.Landscape @@ -254,23 +262,44 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : ) { buttonBeingConfigured = button buttonBeingConfigured!!.onConfigureTouch(event) + touchStartX = event.getX(pointerIndex) + touchStartY = event.getY(pointerIndex) + hasMoved = false } MotionEvent.ACTION_MOVE -> if (buttonBeingConfigured != null) { - buttonBeingConfigured!!.onConfigureTouch(event) - invalidate() - return true + val moveDistance = kotlin.math.sqrt( + (event.getX(pointerIndex) - touchStartX).let { it * it } + + (event.getY(pointerIndex) - touchStartY).let { it * it } + ) + + if (moveDistance > moveThreshold) { + hasMoved = true + buttonBeingConfigured!!.onConfigureTouch(event) + invalidate() + return true + } } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { - // Persist button position by saving new place. - saveControlPosition( - buttonBeingConfigured!!.overlayControlData.id, - buttonBeingConfigured!!.bounds.centerX(), - buttonBeingConfigured!!.bounds.centerY(), - layout - ) + if (!hasMoved) { + showScaleDialog( + buttonBeingConfigured, + null, + null, + fingerPositionX, + fingerPositionY + ) + } else { + saveControlPosition( + buttonBeingConfigured!!.overlayControlData.id, + buttonBeingConfigured!!.bounds.centerX(), + buttonBeingConfigured!!.bounds.centerY(), + individuaScale = buttonBeingConfigured!!.overlayControlData.individualScale, + layout + ) + } buttonBeingConfigured = null } } @@ -287,23 +316,46 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : ) { dpadBeingConfigured = dpad dpadBeingConfigured!!.onConfigureTouch(event) + touchStartX = event.getX(pointerIndex) + touchStartY = event.getY(pointerIndex) + hasMoved = false } MotionEvent.ACTION_MOVE -> if (dpadBeingConfigured != null) { - dpadBeingConfigured!!.onConfigureTouch(event) - invalidate() - return true + val moveDistance = kotlin.math.sqrt( + (event.getX(pointerIndex) - touchStartX).let { it * it } + + (event.getY(pointerIndex) - touchStartY).let { it * it } + ) + + if (moveDistance > moveThreshold) { + hasMoved = true + dpadBeingConfigured!!.onConfigureTouch(event) + invalidate() + return true + } } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) { - // Persist button position by saving new place. - saveControlPosition( - OverlayControl.COMBINED_DPAD.id, - dpadBeingConfigured!!.bounds.centerX(), - dpadBeingConfigured!!.bounds.centerY(), - layout - ) + if (!hasMoved) { + // This was a click, show scale dialog for dpad + showScaleDialog( + null, + dpadBeingConfigured, + null, + fingerPositionX, + fingerPositionY + ) + } else { + // This was a move, save position + saveControlPosition( + OverlayControl.COMBINED_DPAD.id, + dpadBeingConfigured!!.bounds.centerX(), + dpadBeingConfigured!!.bounds.centerY(), + individuaScale = dpadBeingConfigured!!.individualScale, + layout + ) + } dpadBeingConfigured = null } } @@ -317,21 +369,43 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : ) { joystickBeingConfigured = joystick joystickBeingConfigured!!.onConfigureTouch(event) + touchStartX = event.getX(pointerIndex) + touchStartY = event.getY(pointerIndex) + hasMoved = false } MotionEvent.ACTION_MOVE -> if (joystickBeingConfigured != null) { - joystickBeingConfigured!!.onConfigureTouch(event) - invalidate() + val moveDistance = kotlin.math.sqrt( + (event.getX(pointerIndex) - touchStartX).let { it * it } + + (event.getY(pointerIndex) - touchStartY).let { it * it } + ) + + if (moveDistance > moveThreshold) { + hasMoved = true + joystickBeingConfigured!!.onConfigureTouch(event) + invalidate() + } } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) { - saveControlPosition( - joystickBeingConfigured!!.prefId, - joystickBeingConfigured!!.bounds.centerX(), - joystickBeingConfigured!!.bounds.centerY(), - layout - ) + if (!hasMoved) { + showScaleDialog( + null, + null, + joystickBeingConfigured, + fingerPositionX, + fingerPositionY + ) + } else { + saveControlPosition( + joystickBeingConfigured!!.prefId, + joystickBeingConfigured!!.bounds.centerX(), + joystickBeingConfigured!!.bounds.centerY(), + individuaScale = joystickBeingConfigured!!.individualScale, + layout + ) + } joystickBeingConfigured = null } } @@ -607,25 +681,117 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : invalidate() } - private fun saveControlPosition(id: String, x: Int, y: Int, layout: OverlayLayout) { + private fun saveControlPosition( + id: String, + x: Int, + y: Int, + individuaScale: Float, + layout: OverlayLayout + ) { val windowSize = getSafeScreenSize(context, Pair(measuredWidth, measuredHeight)) val min = windowSize.first val max = windowSize.second val overlayControlData = NativeConfig.getOverlayControlData() val data = overlayControlData.firstOrNull { it.id == id } val newPosition = Pair((x - min.x).toDouble() / max.x, (y - min.y).toDouble() / max.y) + when (layout) { OverlayLayout.Landscape -> data?.landscapePosition = newPosition OverlayLayout.Portrait -> data?.portraitPosition = newPosition OverlayLayout.Foldable -> data?.foldablePosition = newPosition + } + + data?.individualScale = individuaScale + NativeConfig.setOverlayControlData(overlayControlData) } fun setIsInEditMode(editMode: Boolean) { inEditMode = editMode + if (!editMode) { + scaleDialog?.dismiss() + scaleDialog = null + } } + private fun showScaleDialog( + button: InputOverlayDrawableButton?, + dpad: InputOverlayDrawableDpad?, + joystick: InputOverlayDrawableJoystick?, + x: Int, y: Int + ) { + val overlayControlData = NativeConfig.getOverlayControlData() + // prevent dialog from being spam opened + scaleDialog?.dismiss() + + + when { + button != null -> { + val buttonData = + overlayControlData.firstOrNull { it.id == button.overlayControlData.id } + if (buttonData != null) { + scaleDialog = + OverlayScaleDialog(context, button.overlayControlData) { newScale -> + saveControlPosition( + button.overlayControlData.id, + button.bounds.centerX(), + button.bounds.centerY(), + individuaScale = newScale, + layout + ) + refreshControls() + } + + scaleDialog?.showDialog(x,y, button.bounds.width(), button.bounds.height()) + + } + } + + dpad != null -> { + val dpadData = + overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id } + if (dpadData != null) { + scaleDialog = OverlayScaleDialog(context, dpadData) { newScale -> + saveControlPosition( + OverlayControl.COMBINED_DPAD.id, + dpad.bounds.centerX(), + dpad.bounds.centerY(), + newScale, + layout + ) + + refreshControls() + } + + scaleDialog?.showDialog(x,y, dpad.bounds.width(), dpad.bounds.height()) + + } + } + + joystick != null -> { + val joystickData = overlayControlData.firstOrNull { it.id == joystick.prefId } + if (joystickData != null) { + scaleDialog = OverlayScaleDialog(context, joystickData) { newScale -> + saveControlPosition( + joystick.prefId, + joystick.bounds.centerX(), + joystick.bounds.centerY(), + individuaScale = newScale, + layout + ) + + refreshControls() + } + + scaleDialog?.showDialog(x,y, joystick.bounds.width(), joystick.bounds.height()) + + } + } + } + } + + /** * Applies and saves all default values for the overlay */ @@ -664,12 +830,24 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : val overlayControlData = NativeConfig.getOverlayControlData() overlayControlData.forEach { it.enabled = OverlayControl.from(it.id)?.defaultVisibility == true + it.individualScale = OverlayControl.from(it.id)?.defaultIndividualScaleResource!! } NativeConfig.setOverlayControlData(overlayControlData) refreshControls() } + fun resetIndividualControlScale() { + val overlayControlData = NativeConfig.getOverlayControlData() + overlayControlData.forEach { data -> + val defaultControlData = OverlayControl.from(data.id) ?: return@forEach + data.individualScale = defaultControlData.defaultIndividualScaleResource + } + NativeConfig.setOverlayControlData(overlayControlData) + NativeConfig.saveGlobalConfig() + refreshControls() + } + private fun defaultOverlayPositionByLayout(layout: OverlayLayout) { val overlayControlData = NativeConfig.getOverlayControlData() for (data in overlayControlData) { @@ -860,6 +1038,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale /= 100f + // Apply individual scale + scale *= overlayControlData.individualScale + // Initialize the InputOverlayDrawableButton. val defaultStateBitmap = getBitmap(context, defaultResId, scale) val pressedStateBitmap = getBitmap(context, pressedResId, scale) @@ -922,11 +1103,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : // Resources handle for fetching the initial Drawable resource. val res = context.resources + // Get the dpad control data for individual scale + val overlayControlData = NativeConfig.getOverlayControlData() + val dpadData = overlayControlData.firstOrNull { it.id == OverlayControl.COMBINED_DPAD.id } + // Decide scale based on button ID and user preference var scale = 0.25f scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale /= 100f + // Apply individual scale + if (dpadData != null) { + scale *= dpadData.individualScale + } + // Initialize the InputOverlayDrawableDpad. val defaultStateBitmap = getBitmap(context, defaultResId, scale) @@ -1000,6 +1190,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) : scale *= (IntSetting.OVERLAY_SCALE.getInt() + 50).toFloat() scale /= 100f + // Apply individual scale + scale *= overlayControlData.individualScale + // Initialize the InputOverlayDrawableJoystick. val bitmapOuter = getBitmap(context, resOuter, scale) val bitmapInnerDefault = getBitmap(context, defaultResInner, 1.0f) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt index 0cb6ff2440..01f07e4f36 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableDpad.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.overlay @@ -42,6 +42,8 @@ class InputOverlayDrawableDpad( val width: Int val height: Int + var individualScale: Float = 1.0f + private val defaultStateBitmap: BitmapDrawable private val pressedOneDirectionStateBitmap: BitmapDrawable private val pressedTwoDirectionsStateBitmap: BitmapDrawable diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt index 4b07107fca..bc3ff15b21 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.overlay @@ -51,6 +51,8 @@ class InputOverlayDrawableJoystick( val width: Int val height: Int + var individualScale: Float = 1.0f + private var opacity: Int = 0 private var virtBounds: Rect diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt new file mode 100644 index 0000000000..f489ef3b7c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/OverlayScaleDialog.kt @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.overlay + +import android.app.Dialog +import android.content.Context +import android.view.Gravity +import android.view.LayoutInflater +import android.view.WindowManager +import android.widget.TextView +import com.google.android.material.button.MaterialButton +import com.google.android.material.slider.Slider +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.overlay.model.OverlayControlData + +class OverlayScaleDialog( + context: Context, + private val overlayControlData: OverlayControlData, + private val onScaleChanged: (Float) -> Unit +) : Dialog(context) { + + private var currentScale = overlayControlData.individualScale + private val originalScale = overlayControlData.individualScale + private lateinit var scaleValueText: TextView + private lateinit var scaleSlider: Slider + + init { + setupDialog() + } + + private fun setupDialog() { + val view = LayoutInflater.from(context).inflate(R.layout.dialog_overlay_scale, null) + setContentView(view) + + window?.setBackgroundDrawable(null) + + window?.apply { + attributes = attributes.apply { + flags = flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND.inv() + flags = flags or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + } + } + + scaleValueText = view.findViewById(R.id.scaleValueText) + scaleSlider = view.findViewById(R.id.scaleSlider) + val resetButton = view.findViewById(R.id.resetButton) + val confirmButton = view.findViewById(R.id.confirmButton) + val cancelButton = view.findViewById(R.id.cancelButton) + + scaleValueText.text = String.format("%.1fx", currentScale) + scaleSlider.value = currentScale + + scaleSlider.addOnChangeListener { _, value, input -> + if (input) { + currentScale = value + scaleValueText.text = String.format("%.1fx", currentScale) + } + } + + scaleSlider.addOnSliderTouchListener(object : Slider.OnSliderTouchListener { + override fun onStartTrackingTouch(slider: Slider) { + // pass + } + + override fun onStopTrackingTouch(slider: Slider) { + onScaleChanged(currentScale) + } + }) + + resetButton.setOnClickListener { + currentScale = 1.0f + scaleSlider.value = 1.0f + scaleValueText.text = String.format("%.1fx", currentScale) + onScaleChanged(currentScale) + } + + confirmButton.setOnClickListener { + overlayControlData.individualScale = currentScale + //slider value is already saved on touch dispatch but just to be sure + onScaleChanged(currentScale) + dismiss() + } + + // both cancel button and back gesture should revert the scale change + cancelButton.setOnClickListener { + onScaleChanged(originalScale) + dismiss() + } + + setOnCancelListener { + onScaleChanged(originalScale) + dismiss() + } + } + + fun showDialog(anchorX: Int, anchorY: Int, anchorHeight: Int, anchorWidth: Int) { + show() + + show() + + // TODO: this calculation is a bit rough, improve it later on + window?.let { window -> + val layoutParams = window.attributes + layoutParams.gravity = Gravity.TOP or Gravity.START + + val density = context.resources.displayMetrics.density + val dialogWidthPx = (320 * density).toInt() + val dialogHeightPx = (400 * density).toInt() // set your estimated dialog height + + val screenHeight = context.resources.displayMetrics.heightPixels + + + layoutParams.x = anchorX + anchorWidth / 2 - dialogWidthPx / 2 + layoutParams.y = anchorY + anchorHeight / 2 - dialogHeightPx / 2 + layoutParams.width = dialogWidthPx + + + window.attributes = layoutParams + } + + } + +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt index a0eeadf4bc..10cc547d0b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControl.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.overlay.model @@ -12,126 +12,144 @@ enum class OverlayControl( val defaultVisibility: Boolean, @IntegerRes val defaultLandscapePositionResources: Pair, @IntegerRes val defaultPortraitPositionResources: Pair, - @IntegerRes val defaultFoldablePositionResources: Pair + @IntegerRes val defaultFoldablePositionResources: Pair, + val defaultIndividualScaleResource: Float, ) { BUTTON_A( "button_a", true, Pair(R.integer.BUTTON_A_X, R.integer.BUTTON_A_Y), Pair(R.integer.BUTTON_A_X_PORTRAIT, R.integer.BUTTON_A_Y_PORTRAIT), - Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE) + Pair(R.integer.BUTTON_A_X_FOLDABLE, R.integer.BUTTON_A_Y_FOLDABLE), + 1.0f ), BUTTON_B( "button_b", true, Pair(R.integer.BUTTON_B_X, R.integer.BUTTON_B_Y), Pair(R.integer.BUTTON_B_X_PORTRAIT, R.integer.BUTTON_B_Y_PORTRAIT), - Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE) + Pair(R.integer.BUTTON_B_X_FOLDABLE, R.integer.BUTTON_B_Y_FOLDABLE), + 1.0f ), BUTTON_X( "button_x", true, Pair(R.integer.BUTTON_X_X, R.integer.BUTTON_X_Y), Pair(R.integer.BUTTON_X_X_PORTRAIT, R.integer.BUTTON_X_Y_PORTRAIT), - Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE) + Pair(R.integer.BUTTON_X_X_FOLDABLE, R.integer.BUTTON_X_Y_FOLDABLE), + 1.0f ), BUTTON_Y( "button_y", true, Pair(R.integer.BUTTON_Y_X, R.integer.BUTTON_Y_Y), Pair(R.integer.BUTTON_Y_X_PORTRAIT, R.integer.BUTTON_Y_Y_PORTRAIT), - Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE) + Pair(R.integer.BUTTON_Y_X_FOLDABLE, R.integer.BUTTON_Y_Y_FOLDABLE), + 1.0f ), BUTTON_PLUS( "button_plus", true, Pair(R.integer.BUTTON_PLUS_X, R.integer.BUTTON_PLUS_Y), Pair(R.integer.BUTTON_PLUS_X_PORTRAIT, R.integer.BUTTON_PLUS_Y_PORTRAIT), - Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE) + Pair(R.integer.BUTTON_PLUS_X_FOLDABLE, R.integer.BUTTON_PLUS_Y_FOLDABLE), + 1.0f ), BUTTON_MINUS( "button_minus", true, Pair(R.integer.BUTTON_MINUS_X, R.integer.BUTTON_MINUS_Y), Pair(R.integer.BUTTON_MINUS_X_PORTRAIT, R.integer.BUTTON_MINUS_Y_PORTRAIT), - Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE) + Pair(R.integer.BUTTON_MINUS_X_FOLDABLE, R.integer.BUTTON_MINUS_Y_FOLDABLE), + 1.0f ), BUTTON_HOME( "button_home", false, Pair(R.integer.BUTTON_HOME_X, R.integer.BUTTON_HOME_Y), Pair(R.integer.BUTTON_HOME_X_PORTRAIT, R.integer.BUTTON_HOME_Y_PORTRAIT), - Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE) + Pair(R.integer.BUTTON_HOME_X_FOLDABLE, R.integer.BUTTON_HOME_Y_FOLDABLE), + 1.0f ), BUTTON_CAPTURE( "button_capture", false, Pair(R.integer.BUTTON_CAPTURE_X, R.integer.BUTTON_CAPTURE_Y), Pair(R.integer.BUTTON_CAPTURE_X_PORTRAIT, R.integer.BUTTON_CAPTURE_Y_PORTRAIT), - Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE) + Pair(R.integer.BUTTON_CAPTURE_X_FOLDABLE, R.integer.BUTTON_CAPTURE_Y_FOLDABLE), + 1.0f ), BUTTON_L( "button_l", true, Pair(R.integer.BUTTON_L_X, R.integer.BUTTON_L_Y), Pair(R.integer.BUTTON_L_X_PORTRAIT, R.integer.BUTTON_L_Y_PORTRAIT), - Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE) + Pair(R.integer.BUTTON_L_X_FOLDABLE, R.integer.BUTTON_L_Y_FOLDABLE), + 1.0f ), BUTTON_R( "button_r", true, Pair(R.integer.BUTTON_R_X, R.integer.BUTTON_R_Y), Pair(R.integer.BUTTON_R_X_PORTRAIT, R.integer.BUTTON_R_Y_PORTRAIT), - Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE) + Pair(R.integer.BUTTON_R_X_FOLDABLE, R.integer.BUTTON_R_Y_FOLDABLE), + 1.0f ), BUTTON_ZL( "button_zl", true, Pair(R.integer.BUTTON_ZL_X, R.integer.BUTTON_ZL_Y), Pair(R.integer.BUTTON_ZL_X_PORTRAIT, R.integer.BUTTON_ZL_Y_PORTRAIT), - Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE) + Pair(R.integer.BUTTON_ZL_X_FOLDABLE, R.integer.BUTTON_ZL_Y_FOLDABLE), + 1.0f ), BUTTON_ZR( "button_zr", true, Pair(R.integer.BUTTON_ZR_X, R.integer.BUTTON_ZR_Y), Pair(R.integer.BUTTON_ZR_X_PORTRAIT, R.integer.BUTTON_ZR_Y_PORTRAIT), - Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE) + Pair(R.integer.BUTTON_ZR_X_FOLDABLE, R.integer.BUTTON_ZR_Y_FOLDABLE), + 1.0f ), BUTTON_STICK_L( "button_stick_l", true, Pair(R.integer.BUTTON_STICK_L_X, R.integer.BUTTON_STICK_L_Y), Pair(R.integer.BUTTON_STICK_L_X_PORTRAIT, R.integer.BUTTON_STICK_L_Y_PORTRAIT), - Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE) + Pair(R.integer.BUTTON_STICK_L_X_FOLDABLE, R.integer.BUTTON_STICK_L_Y_FOLDABLE), + 1.0f ), BUTTON_STICK_R( "button_stick_r", true, Pair(R.integer.BUTTON_STICK_R_X, R.integer.BUTTON_STICK_R_Y), Pair(R.integer.BUTTON_STICK_R_X_PORTRAIT, R.integer.BUTTON_STICK_R_Y_PORTRAIT), - Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE) + Pair(R.integer.BUTTON_STICK_R_X_FOLDABLE, R.integer.BUTTON_STICK_R_Y_FOLDABLE), + 1.0f ), STICK_L( "stick_l", true, Pair(R.integer.STICK_L_X, R.integer.STICK_L_Y), Pair(R.integer.STICK_L_X_PORTRAIT, R.integer.STICK_L_Y_PORTRAIT), - Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE) + Pair(R.integer.STICK_L_X_FOLDABLE, R.integer.STICK_L_Y_FOLDABLE), + 1.0f ), STICK_R( "stick_r", true, Pair(R.integer.STICK_R_X, R.integer.STICK_R_Y), Pair(R.integer.STICK_R_X_PORTRAIT, R.integer.STICK_R_Y_PORTRAIT), - Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE) + Pair(R.integer.STICK_R_X_FOLDABLE, R.integer.STICK_R_Y_FOLDABLE), + 1.0f ), COMBINED_DPAD( "combined_dpad", true, Pair(R.integer.COMBINED_DPAD_X, R.integer.COMBINED_DPAD_Y), Pair(R.integer.COMBINED_DPAD_X_PORTRAIT, R.integer.COMBINED_DPAD_Y_PORTRAIT), - Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE) + Pair(R.integer.COMBINED_DPAD_X_FOLDABLE, R.integer.COMBINED_DPAD_Y_FOLDABLE), + 1.0f ); fun getDefaultPositionForLayout(layout: OverlayLayout): Pair { @@ -173,7 +191,8 @@ enum class OverlayControl( defaultVisibility, getDefaultPositionForLayout(OverlayLayout.Landscape), getDefaultPositionForLayout(OverlayLayout.Portrait), - getDefaultPositionForLayout(OverlayLayout.Foldable) + getDefaultPositionForLayout(OverlayLayout.Foldable), + defaultIndividualScaleResource ) companion object { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt index 26cfeb1db5..6cc5a59c98 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/model/OverlayControlData.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.overlay.model @@ -8,7 +8,8 @@ data class OverlayControlData( var enabled: Boolean, var landscapePosition: Pair, var portraitPosition: Pair, - var foldablePosition: Pair + var foldablePosition: Pair, + var individualScale: Float ) { fun positionFromLayout(layout: OverlayLayout): Pair = when (layout) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt index de0794a17f..5325f688b6 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.utils @@ -170,7 +170,8 @@ object DirectoryInitialization { buttonEnabled, Pair(landscapeXPosition, landscapeYPosition), Pair(portraitXPosition, portraitYPosition), - Pair(foldableXPosition, foldableYPosition) + Pair(foldableXPosition, foldableYPosition), + OverlayControl.map[buttonId]?.defaultIndividualScaleResource ?: 1.0f ) overlayControlDataMap[buttonId] = controlData setOverlayData = true diff --git a/src/android/app/src/main/jni/android_config.cpp b/src/android/app/src/main/jni/android_config.cpp index a79a64afbb..41ac680d6b 100644 --- a/src/android/app/src/main/jni/android_config.cpp +++ b/src/android/app/src/main/jni/android_config.cpp @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later #include #include @@ -103,6 +103,7 @@ void AndroidConfig::ReadOverlayValues() { ReadDoubleSetting(std::string("foldable\\x_position")); control_data.foldable_position.second = ReadDoubleSetting(std::string("foldable\\y_position")); + control_data.individual_scale = static_cast(ReadDoubleSetting(std::string("individual_scale"))); AndroidSettings::values.overlay_control_data.push_back(control_data); } EndArray(); @@ -255,6 +256,7 @@ void AndroidConfig::SaveOverlayValues() { control_data.foldable_position.first); WriteDoubleSetting(std::string("foldable\\y_position"), control_data.foldable_position.second); + WriteDoubleSetting(std::string("individual_scale"), static_cast(control_data.individual_scale)); } EndArray(); diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index cd18f1e5b3..16735531ee 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -24,6 +24,7 @@ namespace AndroidSettings { std::pair landscape_position; std::pair portrait_position; std::pair foldable_position; + float individual_scale; }; struct Values { diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 0b26280c6c..e6021ed217 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later #include @@ -369,7 +369,9 @@ jobjectArray Java_org_yuzu_yuzu_1emu_utils_NativeConfig_getOverlayControlData(JN env->NewObject(Common::Android::GetOverlayControlDataClass(), Common::Android::GetOverlayControlDataConstructor(), Common::Android::ToJString(env, control_data.id), control_data.enabled, - jlandscapePosition, jportraitPosition, jfoldablePosition); + jlandscapePosition, jportraitPosition, jfoldablePosition, + control_data.individual_scale); + env->SetObjectArrayElement(joverlayControlDataArray, i, jcontrolData); } return joverlayControlDataArray; @@ -418,9 +420,12 @@ void Java_org_yuzu_yuzu_1emu_utils_NativeConfig_setOverlayControlData( env, env->GetObjectField(jfoldablePosition, Common::Android::GetPairSecondField()))); + float individual_scale = static_cast(env->GetFloatField( + joverlayControlData, Common::Android::GetOverlayControlDataIndividualScaleField())); + AndroidSettings::values.overlay_control_data.push_back(AndroidSettings::OverlayControlData{ Common::Android::GetJString(env, jidString), enabled, landscape_position, - portrait_position, foldable_position}); + portrait_position, foldable_position, individual_scale}); } } diff --git a/src/android/app/src/main/res/layout/dialog_overlay_scale.xml b/src/android/app/src/main/res/layout/dialog_overlay_scale.xml new file mode 100644 index 0000000000..9e047f211e --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_overlay_scale.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c05420a17f..d41246b184 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -855,6 +855,7 @@ Touchscreen Lock drawer Unlock drawer + Reset Loading settings… diff --git a/src/common/android/id_cache.cpp b/src/common/android/id_cache.cpp index e0edd006a5..1198833996 100644 --- a/src/common/android/id_cache.cpp +++ b/src/common/android/id_cache.cpp @@ -1,7 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -49,6 +46,7 @@ static jclass s_overlay_control_data_class; static jmethodID s_overlay_control_data_constructor; static jfieldID s_overlay_control_data_id_field; static jfieldID s_overlay_control_data_enabled_field; +static jfieldID s_overlay_control_data_individual_scale_field; static jfieldID s_overlay_control_data_landscape_position_field; static jfieldID s_overlay_control_data_portrait_position_field; static jfieldID s_overlay_control_data_foldable_position_field; @@ -244,6 +242,10 @@ namespace Common::Android { return s_overlay_control_data_enabled_field; } + jfieldID GetOverlayControlDataIndividualScaleField() { + return s_overlay_control_data_individual_scale_field; + } + jfieldID GetOverlayControlDataLandscapePositionField() { return s_overlay_control_data_landscape_position_field; } @@ -494,7 +496,7 @@ namespace Common::Android { reinterpret_cast(env->NewGlobalRef(overlay_control_data_class)); s_overlay_control_data_constructor = env->GetMethodID(overlay_control_data_class, "", - "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;)V"); + "(Ljava/lang/String;ZLkotlin/Pair;Lkotlin/Pair;Lkotlin/Pair;F)V"); s_overlay_control_data_id_field = env->GetFieldID(overlay_control_data_class, "id", "Ljava/lang/String;"); s_overlay_control_data_enabled_field = @@ -505,6 +507,8 @@ namespace Common::Android { env->GetFieldID(overlay_control_data_class, "portraitPosition", "Lkotlin/Pair;"); s_overlay_control_data_foldable_position_field = env->GetFieldID(overlay_control_data_class, "foldablePosition", "Lkotlin/Pair;"); + s_overlay_control_data_individual_scale_field = + env->GetFieldID(overlay_control_data_class, "individualScale", "F"); env->DeleteLocalRef(overlay_control_data_class); const jclass patch_class = env->FindClass("org/yuzu/yuzu_emu/model/Patch"); diff --git a/src/common/android/id_cache.h b/src/common/android/id_cache.h index c56ffcf5c6..6b48f471b7 100644 --- a/src/common/android/id_cache.h +++ b/src/common/android/id_cache.h @@ -1,7 +1,4 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -// SPDX-FileCopyrightText: 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -67,6 +64,7 @@ jclass GetOverlayControlDataClass(); jmethodID GetOverlayControlDataConstructor(); jfieldID GetOverlayControlDataIdField(); jfieldID GetOverlayControlDataEnabledField(); +jfieldID GetOverlayControlDataIndividualScaleField(); jfieldID GetOverlayControlDataLandscapePositionField(); jfieldID GetOverlayControlDataPortraitPositionField(); jfieldID GetOverlayControlDataFoldablePositionField(); From 03c7d6ce4a7919aacd9130bb7e44ab30e9fe387b Mon Sep 17 00:00:00 2001 From: nyx-ynx Date: Mon, 29 Sep 2025 22:40:03 +0200 Subject: [PATCH 10/20] [android] input over(lay)haul 1: Auto-hide input overlay setting (#493) This is step 1 of https://git.eden-emu.dev/eden-emu/eden/issues/47 which was the easiest to implement. How was this not implemented on yuzu already? Would prefer if more people tested this than the usual amount. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/493 Reviewed-by: MaranBr Co-authored-by: nyx-ynx Co-committed-by: nyx-ynx --- .../yuzu_emu/activities/EmulationActivity.kt | 35 ++++++++ .../features/settings/model/BooleanSetting.kt | 2 + .../features/settings/model/IntSetting.kt | 3 +- .../features/settings/model/Settings.kt | 1 + .../settings/model/view/SettingsItem.kt | 17 ++++ .../settings/model/view/SpinBoxSetting.kt | 41 ++++++++++ .../features/settings/ui/SettingsAdapter.kt | 16 +++- .../settings/ui/SettingsDialogFragment.kt | 82 +++++++++++++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 16 ++++ .../ui/viewholder/SpinBoxViewHolder.kt | 44 ++++++++++ .../yuzu_emu/fragments/EmulationFragment.kt | 76 ++++++++++++++++- .../app/src/main/jni/android_settings.h | 9 ++ .../src/main/res/layout/dialog_spinbox.xml | 55 +++++++++++++ .../app/src/main/res/values/strings.xml | 18 ++++ 14 files changed, 409 insertions(+), 6 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt create mode 100644 src/android/app/src/main/res/layout/dialog_spinbox.xml diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt index 2046af42c8..da40453497 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt @@ -68,6 +68,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { var isActivityRecreated = false private lateinit var nfcReader: NfcReader + private var touchDownTime: Long = 0 + private val maxTapDuration = 500L + private val gyro = FloatArray(3) private val accel = FloatArray(3) private var motionTimestamp: Long = 0 @@ -489,6 +492,38 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener { } } + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as? NavHostFragment + val emulationFragment = navHostFragment?.childFragmentManager?.fragments?.firstOrNull() as? org.yuzu.yuzu_emu.fragments.EmulationFragment + + emulationFragment?.let { fragment -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + touchDownTime = System.currentTimeMillis() + // show overlay immediately on touch and cancel timer + if (!emulationViewModel.drawerOpen.value) { + fragment.handler.removeCallbacksAndMessages(null) + fragment.showOverlay() + } + } + MotionEvent.ACTION_UP -> { + if (!emulationViewModel.drawerOpen.value) { + val touchDuration = System.currentTimeMillis() - touchDownTime + + if (touchDuration <= maxTapDuration) { + fragment.handleScreenTap(false) + } else { + // just start the auto-hide timer without toggling visibility + fragment.handleScreenTap(true) + } + } + } + } + } + + return super.dispatchTouchEvent(event) + } + fun onEmulationStarted() { emulationViewModel.setEmulationStarted(true) } diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt index 3c5b9003de..638e1101db 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/BooleanSetting.kt @@ -55,6 +55,8 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting { FRAME_INTERPOLATION("frame_interpolation"), // FRAME_SKIPPING("frame_skipping"), + ENABLE_INPUT_OVERLAY_AUTO_HIDE("enable_input_overlay_auto_hide"), + PERF_OVERLAY_BACKGROUND("perf_overlay_background"), SHOW_PERFORMANCE_OVERLAY("show_performance_overlay"), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt index 21aad8b5d1..d5556a337b 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/IntSetting.kt @@ -59,7 +59,8 @@ enum class IntSetting(override val key: String) : AbstractIntSetting { OFFLINE_WEB_APPLET("offline_web_applet_mode"), LOGIN_SHARE_APPLET("login_share_applet_mode"), WIFI_WEB_AUTH_APPLET("wifi_web_auth_applet_mode"), - MY_PAGE_APPLET("my_page_applet_mode") + MY_PAGE_APPLET("my_page_applet_mode"), + INPUT_OVERLAY_AUTO_HIDE("input_overlay_auto_hide") ; override fun getInt(needsGlobal: Boolean): Int = NativeConfig.getInt(key, needsGlobal) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt index a52f582031..454d0e4807 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt @@ -12,6 +12,7 @@ object Settings { SECTION_SYSTEM(R.string.preferences_system), SECTION_RENDERER(R.string.preferences_graphics), SECTION_PERFORMANCE_STATS(R.string.stats_overlay_options), + SECTION_INPUT_OVERLAY(R.string.input_overlay_options), SECTION_SOC_OVERLAY(R.string.soc_overlay_options), SECTION_AUDIO(R.string.preferences_audio), SECTION_INPUT(R.string.preferences_controls), diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 1f2ba81a73..5f7f7a43f9 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -96,6 +96,7 @@ abstract class SettingsItem( const val TYPE_INT_SINGLE_CHOICE = 9 const val TYPE_INPUT_PROFILE = 10 const val TYPE_STRING_INPUT = 11 + const val TYPE_SPINBOX = 12 const val FASTMEM_COMBINED = "fastmem_combined" @@ -385,6 +386,22 @@ abstract class SettingsItem( warningMessage = R.string.warning_resolution ) ) + put( + SwitchSetting( + BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE, + titleId = R.string.enable_input_overlay_auto_hide, + ) + ) + put( + SpinBoxSetting( + IntSetting.INPUT_OVERLAY_AUTO_HIDE, + titleId = R.string.overlay_auto_hide, + descriptionId = R.string.overlay_auto_hide_description, + min = 1, + max = 999, + valueHint = R.string.seconds + ) + ) put( SwitchSetting( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt new file mode 100644 index 0000000000..0b0d01dfe0 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SpinBoxSetting.kt @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.model.view + +import androidx.annotation.StringRes +import org.yuzu.yuzu_emu.features.settings.model.AbstractByteSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractFloatSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractIntSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractSetting +import org.yuzu.yuzu_emu.features.settings.model.AbstractShortSetting + +class SpinBoxSetting( + setting: AbstractSetting, + @StringRes titleId: Int = 0, + titleString: String = "", + @StringRes descriptionId: Int = 0, + descriptionString: String = "", + val valueHint: Int, + val min: Int, + val max: Int +) : SettingsItem(setting, titleId, titleString, descriptionId, descriptionString) { + override val type = TYPE_SPINBOX + + fun getSelectedValue(needsGlobal: Boolean = false) = + when (setting) { + is AbstractByteSetting -> setting.getByte(needsGlobal).toInt() + is AbstractShortSetting -> setting.getShort(needsGlobal).toInt() + is AbstractIntSetting -> setting.getInt(needsGlobal) + is AbstractFloatSetting -> setting.getFloat(needsGlobal).toInt() + else -> 0 + } + + fun setSelectedValue(value: Int) = + when (setting) { + is AbstractByteSetting -> setting.setByte(value.toByte()) + is AbstractShortSetting -> setting.setShort(value.toShort()) + is AbstractFloatSetting -> setting.setFloat(value.toFloat()) + else -> (setting as AbstractIntSetting).setInt(value) + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt index 500ac6e66e..bdc51b7070 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsAdapter.kt @@ -1,5 +1,5 @@ -// SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later package org.yuzu.yuzu_emu.features.settings.ui @@ -61,6 +61,10 @@ class SettingsAdapter( SliderViewHolder(ListItemSettingBinding.inflate(inflater), this) } + SettingsItem.TYPE_SPINBOX -> { + SpinBoxViewHolder(ListItemSettingBinding.inflate(inflater), this) + } + SettingsItem.TYPE_SUBMENU -> { SubmenuViewHolder(ListItemSettingBinding.inflate(inflater), this) } @@ -191,6 +195,14 @@ class SettingsAdapter( position ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) } + fun onSpinBoxClick(item: SpinBoxSetting, position: Int) { + SettingsDialogFragment.newInstance( + settingsViewModel, + item, + SettingsItem.TYPE_SPINBOX, + position + ).show(fragment.childFragmentManager, SettingsDialogFragment.TAG) + } fun onSubmenuClick(item: SubmenuSetting) { val action = SettingsNavigationDirections.actionGlobalSettingsFragment(item.menuKey, null) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt index dc9f561eca..2a97f15892 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsDialogFragment.kt @@ -14,6 +14,7 @@ import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.fragment.app.DialogFragment import androidx.fragment.app.activityViewModels @@ -22,6 +23,7 @@ import com.google.android.material.slider.Slider import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.databinding.DialogEditTextBinding import org.yuzu.yuzu_emu.databinding.DialogSliderBinding +import org.yuzu.yuzu_emu.databinding.DialogSpinboxBinding import org.yuzu.yuzu_emu.features.input.NativeInput import org.yuzu.yuzu_emu.features.input.model.AnalogDirection import org.yuzu.yuzu_emu.features.settings.model.view.AnalogInputSetting @@ -30,6 +32,7 @@ import org.yuzu.yuzu_emu.features.settings.model.view.IntSingleChoiceSetting import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem import org.yuzu.yuzu_emu.features.settings.model.view.SingleChoiceSetting import org.yuzu.yuzu_emu.features.settings.model.view.SliderSetting +import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting import org.yuzu.yuzu_emu.features.settings.model.view.StringInputSetting import org.yuzu.yuzu_emu.features.settings.model.view.StringSingleChoiceSetting import org.yuzu.yuzu_emu.utils.ParamPackage @@ -46,6 +49,7 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener private lateinit var sliderBinding: DialogSliderBinding private lateinit var stringInputBinding: DialogEditTextBinding + private lateinit var spinboxBinding: DialogSpinboxBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -142,6 +146,76 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener .create() } + SettingsItem.TYPE_SPINBOX -> { + spinboxBinding = DialogSpinboxBinding.inflate(layoutInflater) + val item = settingsViewModel.clickedItem as SpinBoxSetting + + val currentValue = item.getSelectedValue() + spinboxBinding.editValue.setText(currentValue.toString()) + spinboxBinding.textInputLayout.hint = getString(item.valueHint) + + val dialog = MaterialAlertDialogBuilder(requireContext()) + .setTitle(item.title) + .setView(spinboxBinding.root) + .setPositiveButton(android.R.string.ok, this) + .setNegativeButton(android.R.string.cancel, defaultCancelListener) + .create() + + val updateButtonState = { enabled: Boolean -> + dialog.setOnShowListener { dialogInterface -> + (dialogInterface as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled + } + if (dialog.isShowing) { + dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = enabled + } + } + + val updateValidity = { value: Int -> + val isValid = value in item.min..item.max + if (isValid) { + spinboxBinding.textInputLayout.error = null + } else { + spinboxBinding.textInputLayout.error = getString( + if (value < item.min) R.string.value_too_low else R.string.value_too_high, + if (value < item.min) item.min else item.max + ) + } + updateButtonState(isValid) + } + + spinboxBinding.buttonDecrement.setOnClickListener { + val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue + val newValue = current - 1 + spinboxBinding.editValue.setText(newValue.toString()) + updateValidity(newValue) + } + + spinboxBinding.buttonIncrement.setOnClickListener { + val current = spinboxBinding.editValue.text.toString().toIntOrNull() ?: currentValue + val newValue = current + 1 + spinboxBinding.editValue.setText(newValue.toString()) + updateValidity(newValue) + } + + spinboxBinding.editValue.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + override fun afterTextChanged(s: Editable?) { + val value = s.toString().toIntOrNull() + if (value != null) { + updateValidity(value) + } else { + spinboxBinding.textInputLayout.error = getString(R.string.invalid_value) + updateButtonState(false) + } + } + }) + + updateValidity(currentValue) + + dialog + } + SettingsItem.TYPE_STRING_INPUT -> { stringInputBinding = DialogEditTextBinding.inflate(layoutInflater) val item = settingsViewModel.clickedItem as StringInputSetting @@ -281,6 +355,14 @@ class SettingsDialogFragment : DialogFragment(), DialogInterface.OnClickListener sliderSetting.setSelectedValue(settingsViewModel.sliderProgress.value) } + is SpinBoxSetting -> { + val spinBoxSetting = settingsViewModel.clickedItem as SpinBoxSetting + val value = spinboxBinding.editValue.text.toString().toIntOrNull() + if (value != null && value in spinBoxSetting.min..spinBoxSetting.max) { + spinBoxSetting.setSelectedValue(value) + } + } + is StringInputSetting -> { val stringInputSetting = settingsViewModel.clickedItem as StringInputSetting stringInputSetting.setSelectedValue( diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 14d62ceec3..715baec72f 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -97,6 +97,7 @@ class SettingsFragmentPresenter( MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl) MenuTag.SECTION_PERFORMANCE_STATS -> addPerformanceOverlaySettings(sl) MenuTag.SECTION_SOC_OVERLAY -> addSocOverlaySettings(sl) + MenuTag.SECTION_INPUT_OVERLAY -> addInputOverlaySettings(sl) MenuTag.SECTION_AUDIO -> addAudioSettings(sl) MenuTag.SECTION_INPUT -> addInputSettings(sl) MenuTag.SECTION_INPUT_PLAYER_ONE -> addInputPlayer(sl, 0) @@ -156,6 +157,14 @@ class SettingsFragmentPresenter( menuKey = MenuTag.SECTION_SOC_OVERLAY ) ) + add( + SubmenuSetting( + titleId = R.string.input_overlay_options, + iconId = R.drawable.ic_controller, + descriptionId = R.string.input_overlay_options_description, + menuKey = MenuTag.SECTION_INPUT_OVERLAY + ) + ) } add( SubmenuSetting( @@ -264,6 +273,13 @@ class SettingsFragmentPresenter( } } + private fun addInputOverlaySettings(sl: ArrayList) { + sl.apply { + add(BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.key) + add(IntSetting.INPUT_OVERLAY_AUTO_HIDE.key) + } + } + private fun addSocOverlaySettings(sl: ArrayList) { sl.apply { add(HeaderSetting(R.string.stats_overlay_customization)) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt new file mode 100644 index 0000000000..1f9a16b798 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/viewholder/SpinBoxViewHolder.kt @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +package org.yuzu.yuzu_emu.features.settings.ui.viewholder + +import android.view.View +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.databinding.ListItemSettingBinding +import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem +import org.yuzu.yuzu_emu.features.settings.model.view.SpinBoxSetting +import org.yuzu.yuzu_emu.features.settings.ui.SettingsAdapter +import org.yuzu.yuzu_emu.utils.ViewUtils.setVisible + +class SpinBoxViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAdapter) : + SettingViewHolder(binding.root, adapter) { + private lateinit var setting: SpinBoxSetting + + override fun bind(item: SettingsItem) { + setting = item as SpinBoxSetting + binding.textSettingName.text = setting.title + binding.textSettingDescription.setVisible(item.description.isNotEmpty()) + binding.textSettingDescription.text = setting.description + binding.textSettingValue.setVisible(true) + binding.textSettingValue.text = setting.getSelectedValue().toString() + + binding.buttonClear.setVisible(setting.clearable) + binding.buttonClear.setOnClickListener { + adapter.onClearClick(setting, bindingAdapterPosition) + } + + setStyle(setting.isEditable, binding) + } + override fun onClick(clicked: View) { + if (setting.isEditable) { + adapter.onSpinBoxClick(setting, bindingAdapterPosition) + } + } + override fun onLongClick(clicked: View): Boolean { + if (setting.isEditable) { + return adapter.onLongClick(setting, bindingAdapterPosition) + } + return false + } +} \ No newline at end of file diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 4487812ad1..b2d6135372 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -96,6 +96,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private var perfStatsUpdater: (() -> Unit)? = null private var socUpdater: (() -> Unit)? = null + val handler = Handler(Looper.getMainLooper()) + private var isOverlayVisible = true + private var _binding: FragmentEmulationBinding? = null private val binding get() = _binding!! @@ -452,7 +455,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { /** * Ask user if they want to launch with default settings when custom settings fail */ - private suspend fun askUserToLaunchWithDefaultSettings(gameTitle: String, errorMessage: String): Boolean { + private suspend fun askUserToLaunchWithDefaultSettings( + gameTitle: String, + errorMessage: String + ): Boolean { return suspendCoroutine { continuation -> requireActivity().runOnUiThread { MaterialAlertDialogBuilder(requireContext()) @@ -728,6 +734,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { updateShowStatsOverlay() updateSocOverlay() + initializeOverlayAutoHide() + // Re update binding when the specs values get initialized properly binding.inGameMenu.getHeaderView(0).apply { val titleView = findViewById(R.id.text_game_title) @@ -917,6 +925,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { updatePauseMenuEntry(emulationState.isPaused) } } + + // if the overlay auto-hide setting is changed while paused, + // we need to reinitialize the auto-hide timer + initializeOverlayAutoHide() + } private fun resetInputOverlay() { @@ -1035,7 +1048,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { val status = batteryIntent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || - status == BatteryManager.BATTERY_STATUS_FULL + status == BatteryManager.BATTERY_STATUS_FULL if (isCharging) { sb.append(" ${getString(R.string.charging)}") @@ -1728,4 +1741,61 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!) private val socUpdateHandler = Handler(Looper.myLooper()!!) } -} \ No newline at end of file + + private fun startOverlayAutoHideTimer(seconds: Int) { + handler.removeCallbacksAndMessages(null) + + handler.postDelayed({ + if (isOverlayVisible) { + hideOverlay() + } + }, seconds * 1000L) + } + + fun handleScreenTap(isLongTap: Boolean) { + val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt() + val shouldProceed = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() && BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() + + if (!shouldProceed) { + return + } + + // failsafe + if (autoHideSeconds == 0) { + showOverlay() + return + } + + if (!isOverlayVisible && !isLongTap) { + showOverlay() + } + + startOverlayAutoHideTimer(autoHideSeconds) + } + + private fun initializeOverlayAutoHide() { + val autoHideSeconds = IntSetting.INPUT_OVERLAY_AUTO_HIDE.getInt() + val autoHideEnabled = BooleanSetting.ENABLE_INPUT_OVERLAY_AUTO_HIDE.getBoolean() + val showOverlay = BooleanSetting.SHOW_INPUT_OVERLAY.getBoolean() + + if (autoHideEnabled && showOverlay) { + showOverlay() + startOverlayAutoHideTimer(autoHideSeconds) + } + } + + + fun showOverlay() { + if (!isOverlayVisible) { + isOverlayVisible = true + ViewUtils.showView(binding.surfaceInputOverlay, 500) + } + } + + private fun hideOverlay() { + if (isOverlayVisible) { + isOverlayVisible = false + ViewUtils.hideView(binding.surfaceInputOverlay, 500) + } + } +} diff --git a/src/android/app/src/main/jni/android_settings.h b/src/android/app/src/main/jni/android_settings.h index 16735531ee..c9e59ce105 100644 --- a/src/android/app/src/main/jni/android_settings.h +++ b/src/android/app/src/main/jni/android_settings.h @@ -80,6 +80,15 @@ namespace AndroidSettings { Settings::Category::Overlay, Settings::Specialization::Paired, true, true}; + Settings::Setting enable_input_overlay_auto_hide{linkage, false, + "enable_input_overlay_auto_hide", + Settings::Category::Overlay, + Settings::Specialization::Default, true, + true,}; + + Settings::Setting input_overlay_auto_hide{linkage, 5, "input_overlay_auto_hide", + Settings::Category::Overlay, + Settings::Specialization::Default, true, true, &enable_input_overlay_auto_hide}; Settings::Setting perf_overlay_background{linkage, false, "perf_overlay_background", Settings::Category::Overlay, Settings::Specialization::Default, true, diff --git a/src/android/app/src/main/res/layout/dialog_spinbox.xml b/src/android/app/src/main/res/layout/dialog_spinbox.xml new file mode 100644 index 0000000000..2f18ab750d --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_spinbox.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index d41246b184..fd9e1b3f77 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -12,6 +12,24 @@ Eden Eden Switch emulator notifications Eden is Running + Seconds + + + Increment + Decrement + Value + Value must be at least %1$d + Value must be at most %1$d + Invalid value + + + + Overlay Auto Hide + Automatically hide the touch controls overlay after the specified time of inactivity. + Enable Overlay Auto Hide + + Input Overlay + Configure on-screen controls From f422d855b76fcc4867fa08f2dce16da389b038e1 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 30 Sep 2025 02:18:31 +0200 Subject: [PATCH 11/20] [cmake] fix apple, android builds (#2619) Signed-off-by: lizzie Co-authored-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2619 Reviewed-by: MaranBr Co-authored-by: lizzie Co-committed-by: lizzie --- CMakeLists.txt | 47 ++++++++++++++----- .../app/src/main/res/values/strings.xml | 2 +- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5d7126f92..e48263063f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,7 +210,7 @@ endif() option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON) -CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF) +CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "LINUX" OFF) CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF) @@ -894,24 +894,47 @@ if (MSVC AND CXX_CLANG) link_libraries(llvm-mingw-runtime) endif() +#[[ + search order: + - gold (GCC only) - the best, generally, but unfortunately not packaged anymore + - mold (GCC only) - generally does well on GCC + - ldd - preferred on clang + - bfd - the final fallback + - If none are found (macOS uses ld.prime, etc) just use the default linker +]] if (YUZU_USE_FASTER_LD) - # fallback if everything fails (bfd) - set(LINKER bfd) - # clang should always use lld - find_program(LLD lld) - if (LLD) + find_program(LINKER_BFD bfd) + if (LINKER_BFD) + set(LINKER bfd) + endif() + + find_program(LINKER_LLD lld) + if (LINKER_LLD) set(LINKER lld) endif() - # GNU appears to work better with mold - # TODO: mold has been slow lately, see if better options exist (search for gold?) + if (CXX_GCC) - find_program(MOLD mold) - if (MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1") + find_program(LINKER_MOLD mold) + if (LINKER_MOLD AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "12.1") set(LINKER mold) endif() + + find_program(LINKER_GOLD gold) + if (LINKER_GOLD) + set(LINKER gold) + endif() + endif() + + if (LINKER) + message(NOTICE "Selecting ${LINKER} as linker") + add_link_options("-fuse-ld=${LINKER}") + else() + message(WARNING "No faster linker found--using default") + endif() + + if (LINKER STREQUAL "lld" AND CXX_GCC) + message(WARNING "Using lld on GCC may cause issues with certain LTO settings. If the program fails to compile, disable YUZU_USE_FASTER_LD, or install mold or GNU gold.") endif() - message(NOTICE "Selecting ${LINKER} as linker") - add_link_options("-fuse-ld=${LINKER}") endif() # Set runtime library to MD/MDd for all configurations diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index fd9e1b3f77..2a5cc48bb1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -889,7 +889,7 @@ Save/Load Error Fatal Error A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes. - Turning off this setting will significantly degrade performance. It's recommended that you leave this setting enabled. + Turning off this setting will significantly degrade performance. It"s recommended that you leave this setting enabled. Device RAM: %1$s\nRecommended: %2$s %1$s %2$s No bootable game present! From 815d85677a78a1ea846a4da89a8df9ed3d060518 Mon Sep 17 00:00:00 2001 From: Caio Oliveira Date: Tue, 30 Sep 2025 02:51:48 +0200 Subject: [PATCH 12/20] CMake improvements: ccache, bundled Qt, MoltenVK, LTO, and Linux deps (#2622) - Fix YUZU_USE_BUNDLED_QT on Linux (correct path is gcc_64 for Qt 6.8.3). - Implement USE_CCACHE correctly (additional changes on #2580). - Categorize and organize CMake options for clarity. - Add missing Linux dependencies - Set CMP0069 (LTO) default behavior to NEW to reduce warnings. - Replace USE_SYSTEM_MOLTENVK with YUZU_APPLE_USE_BUNDLED_MONTENVK and remove duplicate download_moltenvk. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2622 Reviewed-by: MaranBr Reviewed-by: CamilleLaVey Co-authored-by: Caio Oliveira Co-committed-by: Caio Oliveira --- CMakeLists.txt | 124 +++++++++++------------ CMakeModules/DownloadExternals.cmake | 145 +++++++++++++++------------ docs/Deps.md | 2 +- docs/Options.md | 2 +- src/yuzu/CMakeLists.txt | 4 +- 5 files changed, 144 insertions(+), 133 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e48263063f..3599105020 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,65 +139,50 @@ endif() # Set bundled sdl2/qt as dependent options. # On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion -CMAKE_DEPENDENT_OPTION(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" OFF) - -set(EXT_DEFAULT OFF) - -if (MSVC OR ANDROID) - set(EXT_DEFAULT ON) -endif() +cmake_dependent_option(ENABLE_SDL2 "Enable the SDL2 frontend" ON "NOT ANDROID" OFF) if (ENABLE_SDL2) # TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system - CMAKE_DEPENDENT_OPTION(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF) + cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Compile external SDL2" OFF "NOT MSVC" OFF) option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}") endif() +option(ENABLE_QT "Enable the Qt frontend" ON) +option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) +option(ENABLE_QT_UPDATE_CHECKER "Enable update checker for the Qt frontend" OFF) +cmake_dependent_option(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) +option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF) +option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) +set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries") + +option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) + +set(EXT_DEFAULT OFF) +if (MSVC OR ANDROID) + set(EXT_DEFAULT ON) +endif() +option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT}) + +option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT}) +cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from source" OFF "NOT WIN32 AND NOT ANDROID" OFF) + cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF) cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT WIN32 OR NOT ARCHITECTURE_arm64" OFF) mark_as_advanced(FORCE ENABLE_OPENGL) -option(ENABLE_QT "Enable the Qt frontend" ON) -option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) -option(ENABLE_QT_UPDATE_CHECKER "Enable update checker for the Qt frontend" OFF) - -CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) - -option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT}) - option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) option(ENABLE_WIFI_SCAN "Enable WiFi scanning" OFF) -option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT}) -cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from source" OFF "NOT WIN32 AND NOT ANDROID" OFF) - -option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF) - -option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) - -set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries") - -option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) - -CMAKE_DEPENDENT_OPTION(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF "ENABLE_QT" OFF) +cmake_dependent_option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF "ENABLE_QT" OFF) option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}") -option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ${EXT_DEFAULT}) - -# TODO(crueter): CI this? -option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) - - -CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF) - -CMAKE_DEPENDENT_OPTION(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF) - -CMAKE_DEPENDENT_OPTION(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL2;NOT ANDROID" OFF) - -CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) - +option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF) +if (YUZU_USE_PRECOMPILED_HEADERS) + message(STATUS "Using Precompiled Headers.") + set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON) +endif() option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF) if(YUZU_ENABLE_LTO) include(CheckIPOSupported) @@ -205,17 +190,42 @@ if(YUZU_ENABLE_LTO) if(NOT COMPILER_SUPPORTS_LTO) message(FATAL_ERROR "Your compiler does not support interprocedural optimization (IPO). Re-run CMake with -DYUZU_ENABLE_LTO=OFF.") endif() + set(CMAKE_POLICY_DEFAULT_CMP0069 NEW) set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ${COMPILER_SUPPORTS_LTO}) endif() +option(USE_CCACHE "Use ccache for compilation" OFF) +set(CCACHE_PATH "ccache" CACHE STRING "Path to ccache binary") +if(USE_CCACHE) + find_program(CCACHE_BINARY ${CCACHE_PATH}) + if(CCACHE_BINARY) + message(STATUS "Found ccache at: ${CCACHE_BINARY}") + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_BINARY}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_BINARY}) + if (YUZU_USE_PRECOMPILED_HEADERS) + message(FATAL_ERROR "Precompiled headers are incompatible with ccache. Re-run CMake with -DYUZU_USE_PRECOMPILED_HEADERS=OFF.") + endif() + else() + message(WARNING "USE_CCACHE enabled, but no executable found at: ${CCACHE_PATH}") + endif() +endif() + +# TODO(crueter): CI this? +option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON) + +cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF) +cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF) + +cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "ENABLE_SDL2;NOT ANDROID" OFF) + +cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF) option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON) - -CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "LINUX" OFF) - -CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF) - set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database") +cmake_dependent_option(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "LINUX" OFF) + +cmake_dependent_option(YUZU_APPLE_USE_BUNDLED_MONTENVK "Download bundled MoltenVK lib" ON "APPLE" OFF) + option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF) set(DEFAULT_ENABLE_OPENSSL ON) @@ -228,15 +238,12 @@ if (ANDROID OR WIN32 OR APPLE OR PLATFORM_SUN) # your own copy of it. set(DEFAULT_ENABLE_OPENSSL OFF) endif() - if (ENABLE_WEB_SERVICE) set(DEFAULT_ENABLE_OPENSSL ON) endif() - option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL}) - if (ENABLE_OPENSSL) - CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" "${MSVC}" "NOT ANDROID" ON) + cmake_dependent_option(YUZU_USE_BUNDLED_OPENSSL "Download bundled OpenSSL build" "${MSVC}" "NOT ANDROID" ON) endif() if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL) @@ -263,21 +270,6 @@ if (ANDROID) set(CMAKE_POLICY_VERSION_MINIMUM 3.5) # Workaround for Oboe endif() -if (YUZU_USE_PRECOMPILED_HEADERS) - if (MSVC AND CCACHE) - # buildcache does not properly cache PCH files, leading to compilation errors. - # See https://github.com/mbitsnbites/buildcache/discussions/230 - message(WARNING "buildcache does not properly support Precompiled Headers. Disabling PCH") - set(DYNARMIC_USE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) - set(YUZU_USE_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) - endif() -endif() - -if (YUZU_USE_PRECOMPILED_HEADERS) - message(STATUS "Using Precompiled Headers.") - set(CMAKE_PCH_INSTANTIATE_TEMPLATES ON) -endif() - # Default to a Release build get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE) diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake index 6c4afc03be..f6e3aaa4ad 100644 --- a/CMakeModules/DownloadExternals.cmake +++ b/CMakeModules/DownloadExternals.cmake @@ -10,6 +10,7 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi set(package_base_url "https://github.com/eden-emulator/") set(package_repo "no_platform") set(package_extension "no_platform") + set(CACHE_KEY "") # TODO(crueter): Need to convert ffmpeg to a CI. if (WIN32 OR FORCE_WIN_ARCHIVES) @@ -33,8 +34,9 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi else() message(FATAL_ERROR "No package available for this platform") endif() - set(package_url "${package_base_url}${package_repo}") - set(full_url ${package_url}${remote_path}${lib_name}${package_extension}) + string(CONCAT package_url "${package_base_url}" "${package_repo}") + string(CONCAT full_url "${package_url}" "${remote_path}" "${lib_name}" "${package_extension}") + message(STATUS "Resolved bundled URL: ${full_url}") # TODO(crueter): DELETE THIS ENTIRELY, GLORY BE TO THE CI! AddPackage( @@ -47,26 +49,12 @@ function(download_bundled_external remote_path lib_name cpm_key prefix_var versi # TODO(crueter): hash ) - set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE) - message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}") -endfunction() - -function(download_moltenvk_external platform version) - set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK") - set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") - if (NOT EXISTS ${MOLTENVK_DIR}) - if (NOT EXISTS ${MOLTENVK_TAR}) - file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/${version}/MoltenVK-${platform}.tar - ${MOLTENVK_TAR} SHOW_PROGRESS) - endif() - - execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") + if (DEFINED ${cpm_key}_SOURCE_DIR) + set(${prefix_var} "${${cpm_key}_SOURCE_DIR}" PARENT_SCOPE) + message(STATUS "Using bundled binaries at ${${cpm_key}_SOURCE_DIR}") + else() + message(FATAL_ERROR "AddPackage did not set ${cpm_key}_SOURCE_DIR") endif() - - # Add the MoltenVK library path to the prefix so find_library can locate it. - list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}") - set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) endfunction() # Determine installation parameters for OS, architecture, and compiler @@ -108,7 +96,7 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out set(host "linux") set(type "desktop") set(arch "linux_gcc_64") - set(arch_path "linux") + set(arch_path "gcc_64") endif() set(${host_out} "${host}" PARENT_SCOPE) @@ -143,56 +131,79 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini") if (tool) set(prefix "${base_path}/Tools") - set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target}) + list(APPEND install_args install-tool --outputdir "${base_path}" "${host}" desktop "${target}") else() set(prefix "${base_path}/${target}/${arch_path}") - set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} -m qt_base) + list(APPEND install_args install-qt --outputdir "${base_path}" "${host}" "${type}" "${target}" "${arch}" -m qt_base) if (YUZU_USE_QT_MULTIMEDIA) - set(install_args ${install_args} qtmultimedia) + list(APPEND install_args qtmultimedia) endif() if (YUZU_USE_QT_WEB_ENGINE) - set(install_args ${install_args} qtpositioning qtwebchannel qtwebengine) + list(APPEND install_args qtpositioning qtwebchannel qtwebengine) endif() - if (NOT ${YUZU_QT_MIRROR} STREQUAL "") + if (NOT "${YUZU_QT_MIRROR}" STREQUAL "") message(STATUS "Using Qt mirror ${YUZU_QT_MIRROR}") - set(install_args ${install_args} -b ${YUZU_QT_MIRROR}) + list(APPEND install_args -b "${YUZU_QT_MIRROR}") endif() endif() - message(STATUS "Install Args ${install_args}") + message(STATUS "Install Args: ${install_args}") + if (NOT EXISTS "${prefix}") message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}") set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0") if (WIN32) set(aqt_path "${base_path}/aqt.exe") if (NOT EXISTS "${aqt_path}") - file(DOWNLOAD - ${AQT_PREBUILD_BASE_URL}/aqt.exe - ${aqt_path} SHOW_PROGRESS) + file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt.exe" "${aqt_path}" SHOW_PROGRESS) + endif() + execute_process(COMMAND "${aqt_path}" ${install_args} + WORKING_DIRECTORY "${base_path}" + RESULT_VARIABLE aqt_res + OUTPUT_VARIABLE aqt_out + ERROR_VARIABLE aqt_err) + if (NOT aqt_res EQUAL 0) + message(FATAL_ERROR "aqt.exe failed: ${aqt_err}") endif() - execute_process(COMMAND ${aqt_path} ${install_args} - WORKING_DIRECTORY ${base_path}) elseif (APPLE) set(aqt_path "${base_path}/aqt-macos") if (NOT EXISTS "${aqt_path}") - file(DOWNLOAD - ${AQT_PREBUILD_BASE_URL}/aqt-macos - ${aqt_path} SHOW_PROGRESS) + file(DOWNLOAD "${AQT_PREBUILD_BASE_URL}/aqt-macos" "${aqt_path}" SHOW_PROGRESS) + endif() + execute_process(COMMAND chmod +x "${aqt_path}") + execute_process(COMMAND "${aqt_path}" ${install_args} + WORKING_DIRECTORY "${base_path}" + RESULT_VARIABLE aqt_res + ERROR_VARIABLE aqt_err) + if (NOT aqt_res EQUAL 0) + message(FATAL_ERROR "aqt-macos failed: ${aqt_err}") endif() - execute_process(COMMAND chmod +x ${aqt_path}) - execute_process(COMMAND ${aqt_path} ${install_args} - WORKING_DIRECTORY ${base_path}) else() + find_program(PYTHON3_EXECUTABLE python3) + if (NOT PYTHON3_EXECUTABLE) + message(FATAL_ERROR "python3 is required to install Qt using aqt (pip mode).") + endif() set(aqt_install_path "${base_path}/aqt") file(MAKE_DIRECTORY "${aqt_install_path}") - execute_process(COMMAND python3 -m pip install --target=${aqt_install_path} aqtinstall - WORKING_DIRECTORY ${base_path}) - execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args} - WORKING_DIRECTORY ${base_path}) + execute_process(COMMAND "${PYTHON3_EXECUTABLE}" -m pip install --target="${aqt_install_path}" aqtinstall + WORKING_DIRECTORY "${base_path}" + RESULT_VARIABLE pip_res + ERROR_VARIABLE pip_err) + if (NOT pip_res EQUAL 0) + message(FATAL_ERROR "pip install aqtinstall failed: ${pip_err}") + endif() + + execute_process(COMMAND "${CMAKE_COMMAND}" -E env PYTHONPATH="${aqt_install_path}" "${PYTHON3_EXECUTABLE}" -m aqt ${install_args} + WORKING_DIRECTORY "${base_path}" + RESULT_VARIABLE aqt_res + ERROR_VARIABLE aqt_err) + if (NOT aqt_res EQUAL 0) + message(FATAL_ERROR "aqt (python) failed: ${aqt_err}") + endif() endif() message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}") @@ -210,7 +221,7 @@ endfunction() function(download_qt target) determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path) - get_external_prefix(qt base_path) + set(base_path "${CMAKE_BINARY_DIR}/externals/qt") file(MAKE_DIRECTORY "${base_path}") download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}") @@ -227,26 +238,34 @@ function(download_qt target) set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) endfunction() -function(download_moltenvk) -set(MOLTENVK_PLATFORM "macOS") +function(download_moltenvk version platform) + if(NOT version) + message(FATAL_ERROR "download_moltenvk: version argument is required") + endif() + if(NOT platform) + message(FATAL_ERROR "download_moltenvk: platform argument is required") + endif() -set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK") -set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") -if (NOT EXISTS ${MOLTENVK_DIR}) -if (NOT EXISTS ${MOLTENVK_TAR}) - file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.10-rc2/MoltenVK-all.tar - ${MOLTENVK_TAR} SHOW_PROGRESS) -endif() + set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK") + set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") -execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" - WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") -endif() + if(NOT EXISTS "${MOLTENVK_DIR}") + if(NOT EXISTS "${MOLTENVK_TAR}") + file(DOWNLOAD "https://github.com/KhronosGroup/MoltenVK/releases/download/${version}/MoltenVK-${platform}.tar" + "${MOLTENVK_TAR}" SHOW_PROGRESS) + endif() -# Add the MoltenVK library path to the prefix so find_library can locate it. -list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${MOLTENVK_PLATFORM}") -set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals" + RESULT_VARIABLE tar_res + ERROR_VARIABLE tar_err + ) + if(NOT tar_res EQUAL 0) + message(FATAL_ERROR "Extracting MoltenVK failed: ${tar_err}") + endif() + endif() + list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}") + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) endfunction() -function(get_external_prefix lib_name prefix_var) - set(${prefix_var} "${CMAKE_BINARY_DIR}/externals/${lib_name}" PARENT_SCOPE) -endfunction() diff --git a/docs/Deps.md b/docs/Deps.md index 0e7b7cff62..573d1fe14a 100644 --- a/docs/Deps.md +++ b/docs/Deps.md @@ -101,7 +101,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl Ubuntu, Debian, Mint Linux ```sh -sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev libzydis-dev zydis-tools libzycore-dev +sudo apt-get install autoconf cmake g++ gcc git glslang-tools libasound2t64 libboost-context-dev libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev libmbedtls-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev libzydis-dev zydis-tools libzycore-dev vulkan-utility-libraries-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev ``` * Ubuntu 22.04, Linux Mint 20, or Debian 12 or later is required. diff --git a/docs/Options.md b/docs/Options.md index d19aab63f6..6af91e4918 100644 --- a/docs/Options.md +++ b/docs/Options.md @@ -31,7 +31,7 @@ Notes: * Currently, build fails without this - `YUZU_USE_FASTER_LD` (ON) Check if a faster linker is available * Only available on UNIX -- `USE_SYSTEM_MOLTENVK` (OFF, macOS only) Use the system MoltenVK lib (instead of the bundled one) +- `YUZU_APPLE_USE_BUNDLED_MONTENVK` (ON, macOS only) Download bundled MoltenVK lib) - `YUZU_TZDB_PATH` (string) Path to a pre-downloaded timezone database (useful for nixOS) - `ENABLE_OPENSSL` (ON for Linux and *BSD) Enable OpenSSL backend for the ssl service * Always enabled if the web service is enabled diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index b16c1d99ce..c3d8f5387a 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -366,10 +366,10 @@ if (APPLE) set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE) set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) - if (NOT USE_SYSTEM_MOLTENVK) + if (YUZU_APPLE_USE_BUNDLED_MONTENVK) set(MOLTENVK_PLATFORM "macOS") set(MOLTENVK_VERSION "v1.3.0") - download_moltenvk_external(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION}) + download_moltenvk(${MOLTENVK_PLATFORM} ${MOLTENVK_VERSION}) endif() find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED) message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.") From 2e0a4163cf55fea260d641600b5a1452da27d373 Mon Sep 17 00:00:00 2001 From: Caio Oliveira Date: Tue, 30 Sep 2025 04:25:29 +0200 Subject: [PATCH 13/20] common: include missing headers after PCH disable (#2626) Signed-off-by: Caio Oliveira Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2626 Reviewed-by: crueter Co-authored-by: Caio Oliveira Co-committed-by: Caio Oliveira --- src/common/fs/file.cpp | 1 + src/common/fs/path_util.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index b0b25eb432..722ba41949 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -3,6 +3,7 @@ #include +#include "common/assert.h" #include "common/fs/file.h" #include "common/fs/fs.h" #ifdef ANDROID diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 318f311891..a095e0c239 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -9,6 +9,7 @@ #include #include +#include "common/assert.h" #include "common/fs/fs.h" #ifdef ANDROID #include "common/fs/fs_android.h" From 07fd57309dc0d19910e03dbda5e6764fb4336649 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 29 Aug 2025 23:48:25 +0000 Subject: [PATCH 14/20] [gamemode] Make available on other platforms Signed-off-by: lizzie --- CMakeLists.txt | 2 +- externals/gamemode/gamemode_client.h | 28 +++++++++++++++- src/common/CMakeLists.txt | 8 ++--- src/common/gamemode.cpp | 50 ++++++++++++++++++++++++++++ src/common/gamemode.h | 17 ++++++++++ src/common/linux/gamemode.cpp | 40 ---------------------- src/common/linux/gamemode.h | 24 ------------- src/yuzu/main.cpp | 18 +++------- src/yuzu_cmd/yuzu.cpp | 16 ++------- 9 files changed, 105 insertions(+), 98 deletions(-) create mode 100644 src/common/gamemode.cpp create mode 100644 src/common/gamemode.h delete mode 100644 src/common/linux/gamemode.cpp delete mode 100644 src/common/linux/gamemode.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3599105020..8cb9446d1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -538,7 +538,7 @@ else() find_package(Catch2 3.0.1 REQUIRED) endif() - if (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR ANDROID) + if (PLATFORM_LINUX OR ANDROID) find_package(gamemode 1.7 MODULE) endif() diff --git a/externals/gamemode/gamemode_client.h b/externals/gamemode/gamemode_client.h index b9f64fe460..bfbf61c0c2 100644 --- a/externals/gamemode/gamemode_client.h +++ b/externals/gamemode/gamemode_client.h @@ -1,6 +1,9 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + /* -Copyright (c) 2017-2019, Feral Interactive +Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors All rights reserved. Redistribution and use in source and binary forms, with or without @@ -103,6 +106,7 @@ typedef int (*api_call_pid_return_int)(pid_t); static api_call_return_int REAL_internal_gamemode_request_start = NULL; static api_call_return_int REAL_internal_gamemode_request_end = NULL; static api_call_return_int REAL_internal_gamemode_query_status = NULL; +static api_call_return_int REAL_internal_gamemode_request_restart = NULL; static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; @@ -166,6 +170,10 @@ __attribute__((always_inline)) static inline int internal_load_libgamemode(void) (void **)&REAL_internal_gamemode_query_status, sizeof(REAL_internal_gamemode_query_status), false }, + { "real_gamemode_request_restart", + (void **)&REAL_internal_gamemode_request_restart, + sizeof(REAL_internal_gamemode_request_restart), + false }, { "real_gamemode_error_string", (void **)&REAL_internal_gamemode_error_string, sizeof(REAL_internal_gamemode_error_string), @@ -319,6 +327,24 @@ __attribute__((always_inline)) static inline int gamemode_query_status(void) return REAL_internal_gamemode_query_status(); } +/* Redirect to the real libgamemode */ +__attribute__((always_inline)) static inline int gamemode_request_restart(void) +{ + /* Need to load gamemode */ + if (internal_load_libgamemode() < 0) { + return -1; + } + + if (REAL_internal_gamemode_request_restart == NULL) { + snprintf(internal_gamemode_client_error_string, + sizeof(internal_gamemode_client_error_string), + "gamemode_request_restart missing (older host?)"); + return -1; + } + + return REAL_internal_gamemode_request_restart(); +} + /* Redirect to the real libgamemode */ __attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) { diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 96ea429e5a..ab983f2def 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -68,6 +68,8 @@ add_library( fs/fs_util.h fs/path_util.cpp fs/path_util.h + gamemode.cpp + gamemode.h hash.h heap_tracker.cpp heap_tracker.h @@ -184,11 +186,7 @@ if(ANDROID) android/applets/software_keyboard.h) endif() -if(LINUX AND NOT APPLE) - target_sources(common PRIVATE linux/gamemode.cpp linux/gamemode.h) - - target_link_libraries(common PRIVATE gamemode::headers) -endif() +target_link_libraries(common PRIVATE gamemode::headers) if(ARCHITECTURE_x86_64) target_sources( diff --git a/src/common/gamemode.cpp b/src/common/gamemode.cpp new file mode 100644 index 0000000000..a3f0ba37ab --- /dev/null +++ b/src/common/gamemode.cpp @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +// While technically available on al *NIX platforms, Linux is only available +// as the primary target of libgamemode.so - so warnings are suppressed +#ifdef __unix__ +#include +#endif +#include "common/gamemode.h" +#include "common/logging/log.h" +#include "common/settings.h" + +namespace Common::FeralGamemode { + +void Start() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_start() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +void Stop() noexcept { + if (Settings::values.enable_gamemode) { +#ifdef __unix__ + if (gamemode_request_end() < 0) { +#ifdef __linux__ + LOG_WARNING(Frontend, "{}", gamemode_error_string()); +#else + LOG_INFO(Frontend, "{}", gamemode_error_string()); +#endif + } else { + LOG_INFO(Frontend, "Done"); + } +#endif + } +} + +} // namespace Common::Linux diff --git a/src/common/gamemode.h b/src/common/gamemode.h new file mode 100644 index 0000000000..05b1936bb5 --- /dev/null +++ b/src/common/gamemode.h @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Common::FeralGamemode { + +/// @brief Start the gamemode client +void Start() noexcept; + +/// @brief Stop the gmemode client +void Stop() noexcept; + +} // namespace Common::FeralGamemode diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp deleted file mode 100644 index 8d3e2934a6..0000000000 --- a/src/common/linux/gamemode.cpp +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "common/linux/gamemode.h" -#include "common/logging/log.h" -#include "common/settings.h" - -namespace Common::Linux { - -void StartGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } -} - -void StopGamemode() { - if (Settings::values.enable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } -} - -void SetGamemodeState(bool state) { - if (state) { - StartGamemode(); - } else { - StopGamemode(); - } -} - -} // namespace Common::Linux diff --git a/src/common/linux/gamemode.h b/src/common/linux/gamemode.h deleted file mode 100644 index b80646ae27..0000000000 --- a/src/common/linux/gamemode.h +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -namespace Common::Linux { - -/** - * Start the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StartGamemode(); - -/** - * Stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - */ -void StopGamemode(); - -/** - * Start or stop the (Feral Interactive) Linux gamemode if it is installed and it is activated - * @param state The new state the gamemode should have - */ -void SetGamemodeState(bool state); - -} // namespace Common::Linux diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d2c12c9d40..7fdf8ed9d9 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -22,9 +22,7 @@ #include #include #endif -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" #include @@ -2254,9 +2252,7 @@ void GMainWindow::OnEmulationStopped() { discord_rpc->Update(); -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); // The emulation is stopped, so closing the window or not does not matter anymore disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -3095,10 +3091,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); } void GMainWindow::OnRestartGame() { @@ -3119,10 +3112,7 @@ void GMainWindow::OnPauseGame() { play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif + Common::FeralGamemode::Stop(); } void GMainWindow::OnPauseContinueGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 4a99f34861..f9f83858e4 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -62,10 +62,7 @@ __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif - -#ifdef __linux__ -#include "common/linux/gamemode.h" -#endif +#include "common/gamemode.h" static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 @@ -432,10 +429,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - -#ifdef __linux__ - Common::Linux::StartGamemode(); -#endif + Common::FeralGamemode::StartGamemode(); void(system.Run()); if (system.DebuggerEnabled()) { @@ -447,11 +441,7 @@ int main(int argc, char** argv) { system.DetachDebugger(); void(system.Pause()); system.ShutdownMainProcess(); - -#ifdef __linux__ - Common::Linux::StopGamemode(); -#endif - + Common::FeralGamemode::Stop(); detached_tasks.WaitForAllTasks(); return 0; } From 78cd294df3af22eb29f2770b31e498232bab64bb Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 00:04:36 +0000 Subject: [PATCH 15/20] [gamemode] make option available on all nixes Signed-off-by: lizzie --- src/yuzu/main.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7fdf8ed9d9..d33b5008e3 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -415,10 +415,7 @@ GMainWindow::GMainWindow(bool has_broken_vulkan) #ifdef __unix__ SetupSigInterrupts(); #endif - -#ifdef __linux__ SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); -#endif UISettings::RestoreWindowState(config); @@ -3407,9 +3404,7 @@ 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(); -#ifdef __linux__ const bool old_gamemode = Settings::values.enable_gamemode.GetValue(); -#endif Settings::SetConfiguringGlobal(true); ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), @@ -3469,11 +3464,9 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); } -#ifdef __linux__ if (Settings::values.enable_gamemode.GetValue() != old_gamemode) { SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); } -#endif if (!multiplayer_state->IsHostingPublicRoom()) { multiplayer_state->UpdateCredentials(); @@ -4755,13 +4748,15 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } -#ifdef __linux__ void GMainWindow::SetGamemodeEnabled(bool state) { if (emulation_running) { - Common::Linux::SetGamemodeState(state); + if (state) { + Common::FeralGamemode::Start(); + } else { + Common::FeralGamemode::Stop(); + } } } -#endif void GMainWindow::changeEvent(QEvent* event) { #ifdef __unix__ @@ -4920,7 +4915,9 @@ int main(int argc, char* argv[]) { // the user folder in the Qt Frontend, we need to cd into that working directory const auto bin_path = Common::FS::GetBundleDirectory() / ".."; chdir(Common::FS::PathToUTF8String(bin_path).c_str()); -#elif defined(__unix__) && !defined(__ANDROID__) +#endif + +#ifdef __unix__ // Set the DISPLAY variable in order to open web browsers // TODO (lat9nq): Find a better solution for AppImages to start external applications if (QString::fromLocal8Bit(qgetenv("DISPLAY")).isEmpty()) { From 9677732332b9901082bf95f3c18edc65e9c2dbe8 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 08:43:59 +0000 Subject: [PATCH 16/20] [gamemode] extra win/lin/macos fixes Signed-off-by: lizzie --- externals/CMakeLists.txt | 2 +- src/yuzu/main.cpp | 2 +- src/yuzu_cmd/yuzu.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 754ba61a0b..4c11104de2 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -140,7 +140,7 @@ if (ANDROID AND ARCHITECTURE_arm64) AddJsonPackage(libadrenotools) endif() -if (UNIX AND NOT APPLE AND NOT TARGET gamemode::headers) +if (NOT TARGET gamemode::headers) add_library(gamemode INTERFACE) target_include_directories(gamemode INTERFACE gamemode) add_library(gamemode::headers ALIAS gamemode) diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d33b5008e3..4ab578067d 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3088,7 +3088,7 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); } void GMainWindow::OnRestartGame() { diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index f9f83858e4..8e46f36aec 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -429,7 +429,7 @@ int main(int argc, char** argv) { // Just exit right away. exit(0); }); - Common::FeralGamemode::StartGamemode(); + Common::FeralGamemode::Start(); void(system.Run()); if (system.DebuggerEnabled()) { From 0f8cb492fa41654bfdb0197f2190f7908fa6033b Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 13:40:57 +0000 Subject: [PATCH 17/20] [gamemode] windows fix Signed-off-by: lizzie --- src/yuzu/main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/yuzu/main.h b/src/yuzu/main.h index e3922759b0..c6980968c8 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -315,8 +315,8 @@ private: void SetupSigInterrupts(); static void HandleSigInterrupt(int); void OnSigInterruptNotifierActivated(); - void SetGamemodeEnabled(bool state); #endif + void SetGamemodeEnabled(bool state); Service::AM::FrontendAppletParameters ApplicationAppletParameters(); Service::AM::FrontendAppletParameters LibraryAppletParameters(u64 program_id, From b7f08357b98ed6e297a3e21dca75443a20f283c7 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 13 Sep 2025 20:48:40 +0000 Subject: [PATCH 18/20] [gamemode] default disable on msvc, move to UI category Signed-off-by: lizzie --- src/common/settings.h | 10 ++++++++-- src/common/settings_common.h | 1 - src/qt_common/shared_translation.cpp | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/common/settings.h b/src/common/settings.h index 891bde608c..3b301ccda4 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -616,8 +616,14 @@ struct Values { true, true}; - // Linux - SwitchableSetting enable_gamemode{linkage, true, "enable_gamemode", Category::Linux}; + // Linux/MinGW may support (requires libdl support) + SwitchableSetting enable_gamemode{linkage, +#ifndef _MSC_VER + true, +#else + false, +#endif + "enable_gamemode", Category::UiGeneral}; // Controls InputSetting> players; diff --git a/src/common/settings_common.h b/src/common/settings_common.h index af16ec692b..7902cbf945 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -44,7 +44,6 @@ enum class Category : u32 { Multiplayer, Services, Paths, - Linux, LibraryApplet, MaxEnum, }; diff --git a/src/qt_common/shared_translation.cpp b/src/qt_common/shared_translation.cpp index 8f5d929b74..6c48a7a6c7 100644 --- a/src/qt_common/shared_translation.cpp +++ b/src/qt_common/shared_translation.cpp @@ -429,7 +429,10 @@ std::unique_ptr InitializeTranslations(QObject* parent) tr("Whether or not to check for updates upon startup.")); // Linux - INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QString()); + INSERT(UISettings, + enable_gamemode, + tr("Enable Gamemode"), + QString()); // Ui Debugging From 95f615e2e053b2916c6c0394842d69d73f324f3b Mon Sep 17 00:00:00 2001 From: crueter Date: Fri, 26 Sep 2025 19:30:17 -0400 Subject: [PATCH 19/20] [cmake] vendor gamemode Signed-off-by: crueter --- CMakeLists.txt | 6 +- externals/CMakeLists.txt | 6 +- externals/cpmfile.json | 7 + externals/gamemode/gamemode_client.h | 402 --------------------------- 4 files changed, 12 insertions(+), 409 deletions(-) delete mode 100644 externals/gamemode/gamemode_client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cb9446d1a..4327b5a101 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,7 +312,6 @@ endif() if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX)) set(HAS_NCE 1) add_compile_definitions(HAS_NCE=1) - find_package(oaknut 2.0.1) endif() if (YUZU_ROOM) @@ -538,10 +537,6 @@ else() find_package(Catch2 3.0.1 REQUIRED) endif() - if (PLATFORM_LINUX OR ANDROID) - find_package(gamemode 1.7 MODULE) - endif() - if (ENABLE_OPENSSL) find_package(OpenSSL 1.1.1 REQUIRED) endif() @@ -665,6 +660,7 @@ add_subdirectory(externals) # pass targets from externals find_package(libusb) find_package(VulkanMemoryAllocator) +find_package(gamemode) if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64) find_package(xbyak) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4c11104de2..2d0d5a7401 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -140,9 +140,11 @@ if (ANDROID AND ARCHITECTURE_arm64) AddJsonPackage(libadrenotools) endif() -if (NOT TARGET gamemode::headers) +AddJsonPackage(gamemode) + +if (gamemode_ADDED) add_library(gamemode INTERFACE) - target_include_directories(gamemode INTERFACE gamemode) + target_include_directories(gamemode INTERFACE ${gamemode_SOURCE_DIR}) add_library(gamemode::headers ALIAS gamemode) endif() diff --git a/externals/cpmfile.json b/externals/cpmfile.json index dcafc8f97d..6758957550 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -70,5 +70,12 @@ "sha": "73f3cbb237", "hash": "c08c03063938339d61392b687562909c1a92615b6ef39ec8df19ea472aa6b6478e70d7d5e33d4a27b5d23f7806daf57fe1bacb8124c8a945c918c7663a9e8532", "find_args": "CONFIG" + }, + "gamemode": { + "repo": "FeralInteractive/gamemode", + "sha": "ce6fe122f3", + "hash": "ff62f3c8638528e7afb7336f7c8f9940453d2871446c36f639e3114cc8b5c7f9a817ed209c45e0e061793fc3ee9ba35a4b05140d39f5175c2aacfa0703fddfc4", + "version": "1.7", + "find_args": "MODULE" } } diff --git a/externals/gamemode/gamemode_client.h b/externals/gamemode/gamemode_client.h deleted file mode 100644 index bfbf61c0c2..0000000000 --- a/externals/gamemode/gamemode_client.h +++ /dev/null @@ -1,402 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -// SPDX-License-Identifier: GPL-3.0-or-later - -/* - -Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Feral Interactive nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. - - */ -#ifndef CLIENT_GAMEMODE_H -#define CLIENT_GAMEMODE_H -/* - * GameMode supports the following client functions - * Requests are refcounted in the daemon - * - * int gamemode_request_start() - Request gamemode starts - * 0 if the request was sent successfully - * -1 if the request failed - * - * int gamemode_request_end() - Request gamemode ends - * 0 if the request was sent successfully - * -1 if the request failed - * - * GAMEMODE_AUTO can be defined to make the above two functions apply during static init and - * destruction, as appropriate. In this configuration, errors will be printed to stderr - * - * int gamemode_query_status() - Query the current status of gamemode - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * int gamemode_request_start_for(pid_t pid) - Request gamemode starts for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_request_end_for(pid_t pid) - Request gamemode ends for another process - * 0 if the request was sent successfully - * -1 if the request failed - * -2 if the request was rejected - * - * int gamemode_query_status_for(pid_t pid) - Query status of gamemode for another process - * 0 if gamemode is inactive - * 1 if gamemode is active - * 2 if gamemode is active and this client is registered - * -1 if the query failed - * - * const char* gamemode_error_string() - Get an error string - * returns a string describing any of the above errors - * - * Note: All the above requests can be blocking - dbus requests can and will block while the daemon - * handles the request. It is not recommended to make these calls in performance critical code - */ - -#include -#include - -#include -#include - -#include - -#include - -static char internal_gamemode_client_error_string[512] = { 0 }; - -/** - * Load libgamemode dynamically to dislodge us from most dependencies. - * This allows clients to link and/or use this regardless of runtime. - * See SDL2 for an example of the reasoning behind this in terms of - * dynamic versioning as well. - */ -static volatile int internal_libgamemode_loaded = 1; - -/* Typedefs for the functions to load */ -typedef int (*api_call_return_int)(void); -typedef const char *(*api_call_return_cstring)(void); -typedef int (*api_call_pid_return_int)(pid_t); - -/* Storage for functors */ -static api_call_return_int REAL_internal_gamemode_request_start = NULL; -static api_call_return_int REAL_internal_gamemode_request_end = NULL; -static api_call_return_int REAL_internal_gamemode_query_status = NULL; -static api_call_return_int REAL_internal_gamemode_request_restart = NULL; -static api_call_return_cstring REAL_internal_gamemode_error_string = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_start_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_request_end_for = NULL; -static api_call_pid_return_int REAL_internal_gamemode_query_status_for = NULL; - -/** - * Internal helper to perform the symbol binding safely. - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_bind_libgamemode_symbol( - void *handle, const char *name, void **out_func, size_t func_size, bool required) -{ - void *symbol_lookup = NULL; - char *dl_error = NULL; - - /* Safely look up the symbol */ - symbol_lookup = dlsym(handle, name); - dl_error = dlerror(); - if (required && (dl_error || !symbol_lookup)) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "dlsym failed - %s", - dl_error); - return -1; - } - - /* Have the symbol correctly, copy it to make it usable */ - memcpy(out_func, &symbol_lookup, func_size); - return 0; -} - -/** - * Loads libgamemode and needed functions - * - * Returns 0 on success and -1 on failure - */ -__attribute__((always_inline)) static inline int internal_load_libgamemode(void) -{ - /* We start at 1, 0 is a success and -1 is a fail */ - if (internal_libgamemode_loaded != 1) { - return internal_libgamemode_loaded; - } - - /* Anonymous struct type to define our bindings */ - struct binding { - const char *name; - void **functor; - size_t func_size; - bool required; - } bindings[] = { - { "real_gamemode_request_start", - (void **)&REAL_internal_gamemode_request_start, - sizeof(REAL_internal_gamemode_request_start), - true }, - { "real_gamemode_request_end", - (void **)&REAL_internal_gamemode_request_end, - sizeof(REAL_internal_gamemode_request_end), - true }, - { "real_gamemode_query_status", - (void **)&REAL_internal_gamemode_query_status, - sizeof(REAL_internal_gamemode_query_status), - false }, - { "real_gamemode_request_restart", - (void **)&REAL_internal_gamemode_request_restart, - sizeof(REAL_internal_gamemode_request_restart), - false }, - { "real_gamemode_error_string", - (void **)&REAL_internal_gamemode_error_string, - sizeof(REAL_internal_gamemode_error_string), - true }, - { "real_gamemode_request_start_for", - (void **)&REAL_internal_gamemode_request_start_for, - sizeof(REAL_internal_gamemode_request_start_for), - false }, - { "real_gamemode_request_end_for", - (void **)&REAL_internal_gamemode_request_end_for, - sizeof(REAL_internal_gamemode_request_end_for), - false }, - { "real_gamemode_query_status_for", - (void **)&REAL_internal_gamemode_query_status_for, - sizeof(REAL_internal_gamemode_query_status_for), - false }, - }; - - void *libgamemode = NULL; - - /* Try and load libgamemode */ - libgamemode = dlopen("libgamemode.so.0", RTLD_NOW); - if (!libgamemode) { - /* Attempt to load unversioned library for compatibility with older - * versions (as of writing, there are no ABI changes between the two - - * this may need to change if ever ABI-breaking changes are made) */ - libgamemode = dlopen("libgamemode.so", RTLD_NOW); - if (!libgamemode) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "dlopen failed - %s", - dlerror()); - internal_libgamemode_loaded = -1; - return -1; - } - } - - /* Attempt to bind all symbols */ - for (size_t i = 0; i < sizeof(bindings) / sizeof(bindings[0]); i++) { - struct binding *binder = &bindings[i]; - - if (internal_bind_libgamemode_symbol(libgamemode, - binder->name, - binder->functor, - binder->func_size, - binder->required)) { - internal_libgamemode_loaded = -1; - return -1; - }; - } - - /* Success */ - internal_libgamemode_loaded = 0; - return 0; -} - -/** - * Redirect to the real libgamemode - */ -__attribute__((always_inline)) static inline const char *gamemode_error_string(void) -{ - /* If we fail to load the system gamemode, or we have an error string already, return our error - * string instead of diverting to the system version */ - if (internal_load_libgamemode() < 0 || internal_gamemode_client_error_string[0] != '\0') { - return internal_gamemode_client_error_string; - } - - /* Assert for static analyser that the function is not NULL */ - assert(REAL_internal_gamemode_error_string != NULL); - - return REAL_internal_gamemode_error_string(); -} - -/** - * Redirect to the real libgamemode - * Allow automatically requesting game mode - * Also prints errors as they happen. - */ -#ifdef GAMEMODE_AUTO -__attribute__((constructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_start(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - /* Assert for static analyser that the function is not NULL */ - assert(REAL_internal_gamemode_request_start != NULL); - - if (REAL_internal_gamemode_request_start() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -#ifdef GAMEMODE_AUTO -__attribute__((destructor)) -#else -__attribute__((always_inline)) static inline -#endif -int gamemode_request_end(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - /* Assert for static analyser that the function is not NULL */ - assert(REAL_internal_gamemode_request_end != NULL); - - if (REAL_internal_gamemode_request_end() < 0) { -#ifdef GAMEMODE_AUTO - fprintf(stderr, "gamemodeauto: %s\n", gamemode_error_string()); -#endif - return -1; - } - - return 0; -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_query_status missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status(); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_restart(void) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_restart == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_request_restart missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_restart(); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_start_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_start_for == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_request_start_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_start_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_request_end_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_request_end_for == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_request_end_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_request_end_for(pid); -} - -/* Redirect to the real libgamemode */ -__attribute__((always_inline)) static inline int gamemode_query_status_for(pid_t pid) -{ - /* Need to load gamemode */ - if (internal_load_libgamemode() < 0) { - return -1; - } - - if (REAL_internal_gamemode_query_status_for == NULL) { - snprintf(internal_gamemode_client_error_string, - sizeof(internal_gamemode_client_error_string), - "gamemode_query_status_for missing (older host?)"); - return -1; - } - - return REAL_internal_gamemode_query_status_for(pid); -} - -#endif // CLIENT_GAMEMODE_H From a6ad57078c9ecf472da717119f6ffc2771e8a2b0 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 29 Sep 2025 07:46:59 +0000 Subject: [PATCH 20/20] fix licsense Signed-off-by: lizzie --- src/common/settings_common.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 7902cbf945..0107895c8c 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later