From dfa56765d6d869a317ec46dcf3a8f4f35b146382 Mon Sep 17 00:00:00 2001 From: xcfrg <30675315+xcfrg@users.noreply.github.com> Date: Sun, 16 Jul 2023 18:45:33 -0400 Subject: [PATCH 1/3] yuzu: integrate gamemode support on linux --- externals/CMakeLists.txt | 6 + externals/gamemode/CMakeLists.txt | 7 + externals/gamemode/include/gamemode_client.h | 379 ++++++++++++++++++ src/common/settings.h | 2 + src/yuzu/CMakeLists.txt | 2 +- src/yuzu/configuration/configure_general.cpp | 3 + src/yuzu/configuration/shared_translation.cpp | 1 + src/yuzu/main.cpp | 54 +++ src/yuzu/uisettings.h | 3 + src/yuzu_cmd/CMakeLists.txt | 1 + src/yuzu_cmd/yuzu.cpp | 24 ++ 11 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 externals/gamemode/CMakeLists.txt create mode 100644 externals/gamemode/include/gamemode_client.h diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 515e3f2a4a..36fc60e0ec 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -189,6 +189,12 @@ if (ANDROID) endif() endif() +# Gamemode +if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + add_subdirectory(gamemode) + target_include_directories(gamemode PUBLIC gamemode/include) +endif() + # Breakpad # https://github.com/microsoft/vcpkg/blob/master/ports/breakpad/CMakeLists.txt if (YUZU_CRASH_DUMPS AND NOT TARGET libbreakpad_client) diff --git a/externals/gamemode/CMakeLists.txt b/externals/gamemode/CMakeLists.txt new file mode 100644 index 0000000000..3dddc6dbde --- /dev/null +++ b/externals/gamemode/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +project(gamemode) + +add_library(gamemode include/gamemode_client.h) +set_target_properties(gamemode PROPERTIES LINKER_LANGUAGE C) diff --git a/externals/gamemode/include/gamemode_client.h b/externals/gamemode/include/gamemode_client.h new file mode 100644 index 0000000000..184812334e --- /dev/null +++ b/externals/gamemode/include/gamemode_client.h @@ -0,0 +1,379 @@ +// SPDX-FileCopyrightText: Copyright 2017-2019 Feral Interactive +// SPDX-License-Identifier: BSD-3-Clause + +/* + +Copyright (c) 2017-2019, Feral Interactive +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_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_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_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 diff --git a/src/common/settings.h b/src/common/settings.h index e75099b895..788020bdea 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -178,6 +178,8 @@ struct Values { true, &use_speed_limit}; + Setting enable_gamemode{linkage, false, "enable_gamemode", Category::Core}; + // Cpu SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto, CpuAccuracy::Auto, CpuAccuracy::Paranoid, diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 90278052a8..f3ad2214b5 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -386,7 +386,7 @@ if (NOT WIN32) target_include_directories(yuzu PRIVATE ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) endif() if (UNIX AND NOT APPLE) - target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::DBus) + target_link_libraries(yuzu PRIVATE Qt${QT_MAJOR_VERSION}::DBus gamemode) endif() target_compile_definitions(yuzu PRIVATE diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index c727fadd1e..ce7e17850b 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -29,6 +29,9 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_, if (!Settings::IsConfiguringGlobal()) { ui->button_reset_defaults->setVisible(false); } +#ifndef __linux__ + ui->enable_gamemode->setVisible(false); +#endif } ConfigureGeneral::~ConfigureGeneral() = default; diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index a7b5def32e..903805e755 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -175,6 +175,7 @@ std::unique_ptr InitializeTranslations(QWidget* parent) { INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), QStringLiteral()); INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), QStringLiteral()); + INSERT(UISettings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral()); // Ui Debugging diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index defe45198e..cf61d42584 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -185,6 +185,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif +#ifdef __linux__ +#include +#endif + constexpr int default_mouse_hide_timeout = 2500; constexpr int default_mouse_center_timeout = 10; constexpr int default_input_update_timeout = 1; @@ -2126,6 +2130,16 @@ void GMainWindow::OnEmulationStopped() { discord_rpc->Update(); +#ifdef __linux__ + if (UISettings::values.enable_gamemode) { + if (gamemode_request_end() < 0) { + LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Stopped gamemode"); + } + } +#endif + // The emulation is stopped, so closing the window or not does not matter anymore disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -3504,6 +3518,16 @@ void GMainWindow::OnStartGame() { play_time_manager->Start(); discord_rpc->Update(); + +#ifdef __linux__ + if (UISettings::values.enable_gamemode) { + if (gamemode_request_start() < 0) { + LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Started gamemode"); + } + } +#endif } void GMainWindow::OnRestartGame() { @@ -3524,6 +3548,16 @@ void GMainWindow::OnPauseGame() { play_time_manager->Stop(); UpdateMenuState(); AllowOSSleep(); + +#ifdef __linux__ + if (UISettings::values.enable_gamemode) { + if (gamemode_request_end() < 0) { + LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Stopped gamemode"); + } + } +#endif } void GMainWindow::OnPauseContinueGame() { @@ -5181,6 +5215,26 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } +void GMainWindow::SetGamemodeDisabled([[maybe_unused]] bool state) { +#ifdef __linux__ + if (emulation_running) { + if (state) { + if (gamemode_request_end() < 0) { + LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Stopped gamemode"); + } + } else { + if (gamemode_request_start() < 0) { + LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Started gamemode"); + } + } + } +#endif +} + void GMainWindow::changeEvent(QEvent* event) { #ifdef __unix__ // PaletteChange event appears to only reach so far into the GUI, explicitly asking to diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 549a39e1bd..3e5ddc07ab 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -140,6 +140,9 @@ struct Values { Settings::Specialization::Default, true, true}; + // Gamemode + Setting enable_gamemode{linkage, false, "enable_gamemode", Category::UiGeneral}; + Setting disable_web_applet{linkage, true, "disable_web_applet", Category::Ui}; // Discord RPC diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index fbeba88130..002f3e8417 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -44,6 +44,7 @@ target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2 Vulkan::Headers) if(UNIX AND NOT APPLE) install(TARGETS yuzu-cmd) + target_link_libraries(yuzu-cmd PRIVATE gamemode) endif() if(WIN32) diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 0416d59516..1c3a1809b1 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -63,6 +63,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif +#ifdef __linux__ +#include +#endif + static void PrintHelp(const char* argv0) { std::cout << "Usage: " << argv0 << " [options] \n" @@ -425,6 +429,16 @@ int main(int argc, char** argv) { exit(0); }); +#ifdef __linux__ + if (Settings::values.disable_gamemode) { + if (gamemode_request_start() < 0) { + LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Started gamemode"); + } + } +#endif + void(system.Run()); if (system.DebuggerEnabled()) { system.InitializeDebugger(); @@ -436,6 +450,16 @@ int main(int argc, char** argv) { void(system.Pause()); system.ShutdownMainProcess(); +#ifdef __linux__ + if (Settings::values.disable_gamemode) { + if (gamemode_request_end() < 0) { + LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); + } else { + LOG_INFO(Frontend, "Stopped gamemode"); + } + } +#endif + detached_tasks.WaitForAllTasks(); return 0; } From 40644d43f700cb0075db0eea288078bda7cf4527 Mon Sep 17 00:00:00 2001 From: flodavid Date: Fri, 3 Nov 2023 15:41:16 +0100 Subject: [PATCH 2/3] yuzu: create linux group in general settings - Create files dedicated to starting and stopping gamemode functions - Use them in yuzu and yuzu_cmd modules --- externals/CMakeLists.txt | 3 +- externals/gamemode/CMakeLists.txt | 4 ++ src/common/CMakeLists.txt | 9 +++ src/common/linux/gamemode.cpp | 39 +++++++++++ src/common/linux/gamemode.h | 24 +++++++ src/common/settings.cpp | 2 + src/common/settings.h | 5 +- src/common/settings_common.h | 1 + src/yuzu/configuration/configure_general.cpp | 46 ++++++++++--- src/yuzu/configuration/configure_general.ui | 27 ++++++++ src/yuzu/configuration/configure_system.ui | 2 +- src/yuzu/configuration/shared_translation.cpp | 4 +- src/yuzu/main.cpp | 64 ++++++------------- src/yuzu/main.h | 1 + src/yuzu/uisettings.h | 3 - src/yuzu_cmd/yuzu.cpp | 24 ++----- 16 files changed, 177 insertions(+), 81 deletions(-) create mode 100644 src/common/linux/gamemode.cpp create mode 100644 src/common/linux/gamemode.h diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 36fc60e0ec..4ce9f201e1 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -189,8 +189,7 @@ if (ANDROID) endif() endif() -# Gamemode -if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") +if (UNIX) add_subdirectory(gamemode) target_include_directories(gamemode PUBLIC gamemode/include) endif() diff --git a/externals/gamemode/CMakeLists.txt b/externals/gamemode/CMakeLists.txt index 3dddc6dbde..eb970023db 100644 --- a/externals/gamemode/CMakeLists.txt +++ b/externals/gamemode/CMakeLists.txt @@ -4,4 +4,8 @@ project(gamemode) add_library(gamemode include/gamemode_client.h) + +target_link_libraries(gamemode PRIVATE common) + +target_include_directories(gamemode PUBLIC include) set_target_properties(gamemode PROPERTIES LINKER_LANGUAGE C) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e216eb3dee..57cbb9d072 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -174,6 +174,15 @@ if(ANDROID) ) endif() +if (UNIX) + target_sources(common PRIVATE + linux/gamemode.cpp + linux/gamemode.h + ) + + target_link_libraries(common PRIVATE gamemode) +endif() + if(ARCHITECTURE_x86_64) target_sources(common PRIVATE diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp new file mode 100644 index 0000000000..8876d8dc42 --- /dev/null +++ b/src/common/linux/gamemode.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/linux/gamemode.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 new file mode 100644 index 0000000000..b80646ae27 --- /dev/null +++ b/src/common/linux/gamemode.h @@ -0,0 +1,24 @@ +// 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/common/settings.cpp b/src/common/settings.cpp index a10131eb28..3e829253fe 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -219,6 +219,8 @@ const char* TranslateCategory(Category category) { return "Services"; case Category::Paths: return "Paths"; + case Category::Linux: + return "Linux"; case Category::MaxEnum: break; } diff --git a/src/common/settings.h b/src/common/settings.h index 788020bdea..491f0d3e08 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -178,8 +178,6 @@ struct Values { true, &use_speed_limit}; - Setting enable_gamemode{linkage, false, "enable_gamemode", Category::Core}; - // Cpu SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto, CpuAccuracy::Auto, CpuAccuracy::Paranoid, @@ -429,6 +427,9 @@ struct Values { true, true}; + // Linux + SwitchableSetting enable_gamemode{linkage, true, "enable_gamemode", Category::Linux}; + // Controls InputSetting> players; diff --git a/src/common/settings_common.h b/src/common/settings_common.h index 7943223eba..344c044394 100644 --- a/src/common/settings_common.h +++ b/src/common/settings_common.h @@ -41,6 +41,7 @@ enum class Category : u32 { Multiplayer, Services, Paths, + Linux, MaxEnum, }; diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index ce7e17850b..701b895e70 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -29,9 +29,6 @@ ConfigureGeneral::ConfigureGeneral(const Core::System& system_, if (!Settings::IsConfiguringGlobal()) { ui->button_reset_defaults->setVisible(false); } -#ifndef __linux__ - ui->enable_gamemode->setVisible(false); -#endif } ConfigureGeneral::~ConfigureGeneral() = default; @@ -39,12 +36,29 @@ ConfigureGeneral::~ConfigureGeneral() = default; void ConfigureGeneral::SetConfiguration() {} void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) { - QLayout& layout = *ui->general_widget->layout(); + QLayout& general_layout = *ui->general_widget->layout(); + QLayout& linux_layout = *ui->linux_widget->layout(); - std::map hold{}; + std::map general_hold{}; + std::map linux_hold{}; - for (const auto setting : - UISettings::values.linkage.by_category[Settings::Category::UiGeneral]) { + std::vector settings; + + auto push = [&settings](auto& list) { + for (auto setting : list) { + settings.push_back(setting); + } + }; + + push(UISettings::values.linkage.by_category[Settings::Category::UiGeneral]); + push(Settings::values.linkage.by_category[Settings::Category::Linux]); + + // Only show Linux group on Unix +#ifndef __unix__ + ui->LinuxGroupBox->setVisible(false); +#endif + + for (const auto setting : settings) { auto* widget = builder.BuildWidget(setting, apply_funcs); if (widget == nullptr) { @@ -55,11 +69,23 @@ void ConfigureGeneral::Setup(const ConfigurationShared::Builder& builder) { continue; } - hold.emplace(setting->Id(), widget); + switch (setting->GetCategory()) { + case Settings::Category::UiGeneral: + general_hold.emplace(setting->Id(), widget); + break; + case Settings::Category::Linux: + linux_hold.emplace(setting->Id(), widget); + break; + default: + widget->deleteLater(); + } } - for (const auto& [id, widget] : hold) { - layout.addWidget(widget); + for (const auto& [id, widget] : general_hold) { + general_layout.addWidget(widget); + } + for (const auto& [id, widget] : linux_hold) { + linux_layout.addWidget(widget); } } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index a10e7d3a50..ef20891a32 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -46,6 +46,33 @@ + + + + Linux + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 2a735836e1..04b771129d 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -57,7 +57,7 @@ - + Core diff --git a/src/yuzu/configuration/shared_translation.cpp b/src/yuzu/configuration/shared_translation.cpp index 903805e755..ee0ca4aa73 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -175,7 +175,9 @@ std::unique_ptr InitializeTranslations(QWidget* parent) { INSERT(UISettings, hide_mouse, tr("Hide mouse on inactivity"), QStringLiteral()); INSERT(UISettings, controller_applet_disabled, tr("Disable controller applet"), QStringLiteral()); - INSERT(UISettings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral()); + + // Linux + INSERT(Settings, enable_gamemode, tr("Enable Gamemode"), QStringLiteral()); // Ui Debugging diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index cf61d42584..6ef518b6a8 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -17,6 +17,7 @@ #ifdef __unix__ #include #include +#include "common/linux/gamemode.h" #endif #include @@ -185,10 +186,6 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif -#ifdef __linux__ -#include -#endif - constexpr int default_mouse_hide_timeout = 2500; constexpr int default_mouse_center_timeout = 10; constexpr int default_input_update_timeout = 1; @@ -323,6 +320,7 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulk provider{std::make_unique()} { #ifdef __unix__ SetupSigInterrupts(); + SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); #endif system->Initialize(); @@ -2130,14 +2128,8 @@ void GMainWindow::OnEmulationStopped() { discord_rpc->Update(); -#ifdef __linux__ - if (UISettings::values.enable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } +#ifdef __unix__ + Common::Linux::StopGamemode(); #endif // The emulation is stopped, so closing the window or not does not matter anymore @@ -3519,14 +3511,8 @@ void GMainWindow::OnStartGame() { discord_rpc->Update(); -#ifdef __linux__ - if (UISettings::values.enable_gamemode) { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } +#ifdef __unix__ + Common::Linux::StartGamemode(); #endif } @@ -3549,14 +3535,8 @@ void GMainWindow::OnPauseGame() { UpdateMenuState(); AllowOSSleep(); -#ifdef __linux__ - if (UISettings::values.enable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } +#ifdef __unix__ + Common::Linux::StopGamemode(); #endif } @@ -3839,6 +3819,9 @@ 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 __unix__ + const bool old_gamemode = Settings::values.enable_gamemode.GetValue(); +#endif Settings::SetConfiguringGlobal(true); ConfigureDialog configure_dialog(this, hotkey_registry, input_subsystem.get(), @@ -3900,6 +3883,11 @@ void GMainWindow::OnConfigure() { if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) { SetDiscordEnabled(UISettings::values.enable_discord_presence.GetValue()); } +#ifdef __unix__ + if (Settings::values.enable_gamemode.GetValue() != old_gamemode) { + SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); + } +#endif if (!multiplayer_state->IsHostingPublicRoom()) { multiplayer_state->UpdateCredentials(); @@ -5215,25 +5203,13 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } -void GMainWindow::SetGamemodeDisabled([[maybe_unused]] bool state) { -#ifdef __linux__ +#ifdef __unix__ +void GMainWindow::SetGamemodeEnabled(bool state) { if (emulation_running) { - if (state) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } else { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } + Common::Linux::SetGamemodeState(state); } -#endif } +#endif void GMainWindow::changeEvent(QEvent* event) { #ifdef __unix__ diff --git a/src/yuzu/main.h b/src/yuzu/main.h index c989c079d5..2e13e1834a 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -340,6 +340,7 @@ private: void SetupSigInterrupts(); static void HandleSigInterrupt(int); void OnSigInterruptNotifierActivated(); + void SetGamemodeEnabled(bool state); #endif private slots: diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 3e5ddc07ab..549a39e1bd 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -140,9 +140,6 @@ struct Values { Settings::Specialization::Default, true, true}; - // Gamemode - Setting enable_gamemode{linkage, false, "enable_gamemode", Category::UiGeneral}; - Setting disable_web_applet{linkage, true, "disable_web_applet", Category::Ui}; // Discord RPC diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 1c3a1809b1..a81635fa47 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -63,8 +63,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif -#ifdef __linux__ -#include +#ifdef __unix__ +#include "common/linux/gamemode.h" #endif static void PrintHelp(const char* argv0) { @@ -429,14 +429,8 @@ int main(int argc, char** argv) { exit(0); }); -#ifdef __linux__ - if (Settings::values.disable_gamemode) { - if (gamemode_request_start() < 0) { - LOG_WARNING(Frontend, "Failed to start gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Started gamemode"); - } - } +#ifdef __unix__ + Common::Linux::StartGamemode(); #endif void(system.Run()); @@ -450,14 +444,8 @@ int main(int argc, char** argv) { void(system.Pause()); system.ShutdownMainProcess(); -#ifdef __linux__ - if (Settings::values.disable_gamemode) { - if (gamemode_request_end() < 0) { - LOG_WARNING(Frontend, "Failed to stop gamemode: {}", gamemode_error_string()); - } else { - LOG_INFO(Frontend, "Stopped gamemode"); - } - } +#ifdef __unix__ + Common::Linux::StopGamemode(); #endif detached_tasks.WaitForAllTasks(); From ac11f6e4c5da64db5a6fb2647afbb85164f06086 Mon Sep 17 00:00:00 2001 From: flodavid Date: Fri, 3 Nov 2023 15:57:43 +0100 Subject: [PATCH 3/3] cmake: move gamemode target include into its file --- externals/CMakeLists.txt | 1 - externals/gamemode/CMakeLists.txt | 2 +- src/yuzu_cmd/CMakeLists.txt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 4ce9f201e1..406277fd79 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -191,7 +191,6 @@ endif() if (UNIX) add_subdirectory(gamemode) - target_include_directories(gamemode PUBLIC gamemode/include) endif() # Breakpad diff --git a/externals/gamemode/CMakeLists.txt b/externals/gamemode/CMakeLists.txt index eb970023db..87095642e6 100644 --- a/externals/gamemode/CMakeLists.txt +++ b/externals/gamemode/CMakeLists.txt @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project # SPDX-License-Identifier: GPL-3.0-or-later -project(gamemode) +project(gamemode LANGUAGES CXX C) add_library(gamemode include/gamemode_client.h) diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt index 002f3e8417..fbeba88130 100644 --- a/src/yuzu_cmd/CMakeLists.txt +++ b/src/yuzu_cmd/CMakeLists.txt @@ -44,7 +44,6 @@ target_link_libraries(yuzu-cmd PRIVATE SDL2::SDL2 Vulkan::Headers) if(UNIX AND NOT APPLE) install(TARGETS yuzu-cmd) - target_link_libraries(yuzu-cmd PRIVATE gamemode) endif() if(WIN32)