diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index e9f57b5b5..a31346205 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -319,6 +319,10 @@ if(ANDROID) target_link_libraries(httplib INTERFACE ifaddrs) endif() +if (UNIX AND NOT APPLE) + add_subdirectory(gamemode) +endif() + # cpp-jwt if (ENABLE_WEB_SERVICE) if (USE_SYSTEM_CPP_JWT) diff --git a/externals/gamemode/CMakeLists.txt b/externals/gamemode/CMakeLists.txt new file mode 100644 index 000000000..87095642e --- /dev/null +++ b/externals/gamemode/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +project(gamemode LANGUAGES CXX C) + +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/externals/gamemode/include/gamemode_client.h b/externals/gamemode/include/gamemode_client.h new file mode 100644 index 000000000..184812334 --- /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/citra/citra.cpp b/src/citra/citra.cpp index 4029dd1e3..f44d5d887 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -38,6 +38,10 @@ #include "network/network.h" #include "video_core/renderer_base.h" +#ifdef __unix__ +#include "common/linux/gamemode.h" +#endif + #undef _UNICODE #include #ifndef _MSC_VER @@ -442,6 +446,10 @@ int main(int argc, char** argv) { } } +#ifdef __unix__ + Common::Linux::StartGamemode(); +#endif + std::thread main_render_thread([&emu_window] { emu_window->Present(); }); std::thread secondary_render_thread([&secondary_window] { if (secondary_window) { @@ -493,6 +501,10 @@ int main(int argc, char** argv) { system.Shutdown(); +#ifdef __unix__ + Common::Linux::StopGamemode(); +#endif + detached_tasks.WaitForAllTasks(); return 0; } diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index b33729ffb..a783f121a 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -308,7 +308,7 @@ if (NOT WIN32) endif() if (UNIX AND NOT APPLE) - target_link_libraries(citra-qt PRIVATE Qt6::DBus) + target_link_libraries(citra-qt PRIVATE Qt6::DBus gamemode) endif() target_compile_definitions(citra-qt PRIVATE diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index f194466ce..2a8360221 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -528,6 +528,7 @@ void Config::ReadMiscellaneousValues() { qt_config->beginGroup(QStringLiteral("Miscellaneous")); ReadBasicSetting(Settings::values.log_filter); + ReadBasicSetting(Settings::values.enable_gamemode); qt_config->endGroup(); } @@ -1044,6 +1045,7 @@ void Config::SaveMiscellaneousValues() { qt_config->beginGroup(QStringLiteral("Miscellaneous")); WriteBasicSetting(Settings::values.log_filter); + WriteBasicSetting(Settings::values.enable_gamemode); qt_config->endGroup(); } diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index 781e1a284..bdcb640c9 100644 --- a/src/citra_qt/configuration/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -36,6 +36,9 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) ui->emulation_speed_combo->setVisible(!Settings::IsConfiguringGlobal()); ui->screenshot_combo->setVisible(!Settings::IsConfiguringGlobal()); ui->updateBox->setVisible(UISettings::values.updater_found); +#ifndef __unix__ + ui->toggle_gamemode->setVisible(false); +#endif SetupPerGameUI(); SetConfiguration(); @@ -76,6 +79,9 @@ void ConfigureGeneral::SetConfiguration() { ui->toggle_update_check->setChecked( UISettings::values.check_for_update_on_start.GetValue()); ui->toggle_auto_update->setChecked(UISettings::values.update_on_close.GetValue()); +#ifdef __unix__ + ui->toggle_gamemode->setChecked(Settings::values.enable_gamemode.GetValue()); +#endif } if (Settings::values.frame_limit.GetValue() == 0) { @@ -172,6 +178,9 @@ void ConfigureGeneral::ApplyConfiguration() { UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked(); UISettings::values.update_on_close = ui->toggle_auto_update->isChecked(); +#ifdef __unix__ + Settings::values.enable_gamemode = ui->toggle_gamemode->isChecked(); +#endif } } @@ -201,6 +210,7 @@ void ConfigureGeneral::SetupPerGameUI() { ui->general_group->setVisible(false); ui->updateBox->setVisible(false); ui->button_reset_defaults->setVisible(false); + ui->toggle_gamemode->setVisible(false); ConfigurationShared::SetColoredComboBox( ui->region_combobox, ui->widget_region, diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 4452d69e4..316edadb1 100644 --- a/src/citra_qt/configuration/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -43,6 +43,13 @@ + + + + Enable Gamemode + + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index c462cd14d..934906160 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -26,6 +26,7 @@ #include #include #include +#include "common/linux/gamemode.h" #endif #include "citra_qt/aboutdialog.h" #include "citra_qt/applets/mii_selector.h" @@ -182,6 +183,10 @@ GMainWindow::GMainWindow(Core::System& system_) Debugger::ToggleConsole(); +#ifdef __unix__ + SetGamemodeEnabled(Settings::values.enable_gamemode.GetValue()); +#endif + // register types to use in slots and signals qRegisterMetaType("std::size_t"); qRegisterMetaType("Service::AM::InstallStatus"); @@ -1323,6 +1328,10 @@ void GMainWindow::ShutdownGame() { discord_rpc->Update(); +#ifdef __unix__ + Common::Linux::StopGamemode(); +#endif + // The emulation is stopped, so closing the window or not does not matter anymore disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); disconnect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -1834,6 +1843,10 @@ void GMainWindow::OnStartGame() { discord_rpc->Update(); +#ifdef __unix__ + Common::Linux::StartGamemode(); +#endif + UpdateSaveStates(); UpdateAPIIndicator(); } @@ -1852,6 +1865,10 @@ void GMainWindow::OnPauseGame() { UpdateMenuState(); AllowOSSleep(); + +#ifdef __unix__ + Common::Linux::StopGamemode(); +#endif } void GMainWindow::OnPauseContinueGame() { @@ -2076,15 +2093,25 @@ void GMainWindow::OnConfigure() { const auto old_input_profiles = Settings::values.input_profiles; const auto old_touch_from_button_maps = Settings::values.touch_from_button_maps; const bool old_discord_presence = UISettings::values.enable_discord_presence.GetValue(); +#ifdef __unix__ + const bool old_gamemode = Settings::values.enable_gamemode.GetValue(); +#endif auto result = configureDialog.exec(); game_list->SetDirectoryWatcherEnabled(true); if (result == QDialog::Accepted) { configureDialog.ApplyConfiguration(); InitializeHotkeys(); - if (UISettings::values.theme != old_theme) + if (UISettings::values.theme != old_theme) { UpdateUITheme(); - if (UISettings::values.enable_discord_presence.GetValue() != old_discord_presence) + } + 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(); emit UpdateThemedIcons(); @@ -2931,6 +2958,14 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { discord_rpc->Update(); } +#ifdef __unix__ +void GMainWindow::SetGamemodeEnabled(bool state) { + if (emulation_running) { + Common::Linux::SetGamemodeState(state); + } +} +#endif + #ifdef main #undef main #endif diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index bca836ca8..678f8e04d 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -276,6 +276,9 @@ private: void ShowMouseCursor(); void OpenPerGameConfiguration(u64 title_id, const QString& file_name); void UpdateAPIIndicator(bool update = false); +#ifdef __unix__ + void SetGamemodeEnabled(bool state); +#endif std::unique_ptr ui; Core::System& system; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 64b22e6b1..cba0945c3 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -154,6 +154,15 @@ add_library(citra_common STATIC zstd_compression.h ) +if (UNIX AND NOT APPLE) + target_sources(citra_common PRIVATE + linux/gamemode.cpp + linux/gamemode.h + ) + + target_link_libraries(citra_common PRIVATE gamemode) +endif() + if (APPLE) target_sources(citra_common PUBLIC apple_authorization.h diff --git a/src/common/linux/gamemode.cpp b/src/common/linux/gamemode.cpp new file mode 100644 index 000000000..8876d8dc4 --- /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 000000000..b80646ae2 --- /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.h b/src/common/settings.h index e37e4eef3..ee3cb177f 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -421,6 +421,8 @@ struct Values { std::vector input_profiles; ///< The list of input profiles std::vector touch_from_button_maps; + SwitchableSetting enable_gamemode{true, "enable_gamemode"}; + // Core Setting use_cpu_jit{true, "use_cpu_jit"}; SwitchableSetting cpu_clock_percentage{100, 5, 400, "cpu_clock_percentage"};