diff --git a/CHANGES.md b/CHANGES.md index b4cb9d5968..8936832a99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,5 @@ # Future +- LINUX: Added support for Linux GameMode (https://github.com/FeralInteractive/gamemode), which can be toggled on/off in the Power Management or Latency settings menus. # 1.9.14 - ANDROID/PLAYSTORE: Implement MANAGE_EXTERNAL_STORAGE permission diff --git a/config.def.h b/config.def.h index e00827f52a..25543d7524 100644 --- a/config.def.h +++ b/config.def.h @@ -173,6 +173,8 @@ #define DEFAULT_USER_LANGUAGE 0 +#define DEFAULT_GAMEMODE_ENABLE true + #if (defined(_WIN32) && !defined(_XBOX)) || (defined(__linux) && !defined(ANDROID) && !defined(HAVE_LAKKA)) || (defined(__MACH__) && !defined(IOS)) || defined(EMSCRIPTEN) #define DEFAULT_MOUSE_ENABLE true #else diff --git a/configuration.c b/configuration.c index 29ee793c28..edda27f0bf 100644 --- a/configuration.c +++ b/configuration.c @@ -1993,6 +1993,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("ai_service_enable", &settings->bools.ai_service_enable, true, DEFAULT_AI_SERVICE_ENABLE, false); SETTING_BOOL("ai_service_pause", &settings->bools.ai_service_pause, true, DEFAULT_AI_SERVICE_PAUSE, false); SETTING_BOOL("wifi_enabled", &settings->bools.wifi_enabled, true, DEFAULT_WIFI_ENABLE, false); + SETTING_BOOL("gamemode_enable", &settings->bools.gamemode_enable, true, DEFAULT_GAMEMODE_ENABLE, false); *size = count; @@ -3725,6 +3726,8 @@ static bool config_load_file(global_t *global, if (!config_entry_exists(conf, "user_language")) msg_hash_set_uint(MSG_HASH_USER_LANGUAGE, frontend_driver_get_user_language()); + frontend_driver_set_gamemode(settings->bools.gamemode_enable); + /* If this is the first run of an existing installation * after the independent favourites playlist size limit was * added, set the favourites limit according to the current diff --git a/configuration.h b/configuration.h index 8fffe09c5d..0364ccc2de 100644 --- a/configuration.h +++ b/configuration.h @@ -905,6 +905,8 @@ typedef struct settings bool ai_service_enable; bool ai_service_pause; + + bool gamemode_enable; } bools; } settings_t; diff --git a/deps/feralgamemode/gamemode_client.h b/deps/feralgamemode/gamemode_client.h new file mode 100644 index 0000000000..b6f7afd4c3 --- /dev/null +++ b/deps/feralgamemode/gamemode_client.h @@ -0,0 +1,365 @@ +/* + +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 + +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; + } + + 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; + } + + 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; + } + + 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/frontend/drivers/platform_ctr.c b/frontend/drivers/platform_ctr.c index 4b8e178e17..94eb2f6141 100644 --- a/frontend/drivers/platform_ctr.c +++ b/frontend/drivers/platform_ctr.c @@ -688,6 +688,7 @@ frontend_ctx_driver_t frontend_ctx_ctr = NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "ctr", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_darwin.m b/frontend/drivers/platform_darwin.m index f922e50c55..a3beb70393 100644 --- a/frontend/drivers/platform_darwin.m +++ b/frontend/drivers/platform_darwin.m @@ -1008,6 +1008,7 @@ frontend_ctx_driver_t frontend_ctx_darwin = { NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ #endif + NULL, /* set_gamemode */ "darwin", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_dos.c b/frontend/drivers/platform_dos.c index 9cf690af49..f347b99b39 100644 --- a/frontend/drivers/platform_dos.c +++ b/frontend/drivers/platform_dos.c @@ -210,6 +210,7 @@ frontend_ctx_driver_t frontend_ctx_dos = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "dos", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_emscripten.c b/frontend/drivers/platform_emscripten.c index 2c4b7eff72..eb87b3b578 100644 --- a/frontend/drivers/platform_emscripten.c +++ b/frontend/drivers/platform_emscripten.c @@ -203,6 +203,7 @@ frontend_ctx_driver_t frontend_ctx_emscripten = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "emscripten", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_gx.c b/frontend/drivers/platform_gx.c index 817d7ab1d7..27ca621677 100644 --- a/frontend/drivers/platform_gx.c +++ b/frontend/drivers/platform_gx.c @@ -578,6 +578,7 @@ frontend_ctx_driver_t frontend_ctx_gx = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "gx", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_orbis.c b/frontend/drivers/platform_orbis.c index 4e7d74ce54..1a9e4a703b 100644 --- a/frontend/drivers/platform_orbis.c +++ b/frontend/drivers/platform_orbis.c @@ -372,6 +372,7 @@ frontend_ctx_driver_t frontend_ctx_orbis = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "orbis", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_ps2.c b/frontend/drivers/platform_ps2.c index f0bddb9e8d..842cf59d47 100644 --- a/frontend/drivers/platform_ps2.c +++ b/frontend/drivers/platform_ps2.c @@ -552,6 +552,7 @@ frontend_ctx_driver_t frontend_ctx_ps2 = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "ps2", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_ps3.c b/frontend/drivers/platform_ps3.c index d327bb2c09..fc6e740e71 100644 --- a/frontend/drivers/platform_ps3.c +++ b/frontend/drivers/platform_ps3.c @@ -711,6 +711,7 @@ frontend_ctx_driver_t frontend_ctx_ps3 = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "ps3", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_psp.c b/frontend/drivers/platform_psp.c index aa043872b7..16fcb63191 100644 --- a/frontend/drivers/platform_psp.c +++ b/frontend/drivers/platform_psp.c @@ -662,6 +662,7 @@ frontend_ctx_driver_t frontend_ctx_psp = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "psp", /* ident */ #endif NULL /* get_video_driver */ diff --git a/frontend/drivers/platform_qnx.c b/frontend/drivers/platform_qnx.c index 830cf6f012..ed232229b9 100644 --- a/frontend/drivers/platform_qnx.c +++ b/frontend/drivers/platform_qnx.c @@ -209,6 +209,7 @@ frontend_ctx_driver_t frontend_ctx_qnx = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "qnx", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_switch.c b/frontend/drivers/platform_switch.c index f9d54c418d..1c7d49ee00 100644 --- a/frontend/drivers/platform_switch.c +++ b/frontend/drivers/platform_switch.c @@ -768,6 +768,7 @@ frontend_ctx_driver_t frontend_ctx_switch = NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "switch", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index 722e1c595c..c70732e9c4 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -30,6 +30,10 @@ #ifdef __linux__ #include +#if __STDC_VERSION__ >= 199901L +#include "feralgamemode/gamemode_client.h" +#define FERAL_GAMEMODE +#endif /* inotify API was added in 2.6.13 */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) #define HAS_INOTIFY @@ -1989,6 +1993,44 @@ static void android_app_destroy(struct android_app *android_app) } #endif +static bool frontend_unix_set_gamemode(bool on) +{ +#ifdef FERAL_GAMEMODE + const int gamemode_status = gamemode_query_status(); + + if (gamemode_status < 0) + { + if (on) + { + RARCH_WARN("[GameMode]: GameMode cannot be enabled on this system (\"%s.\") " + "https://github.com/FeralInteractive/gamemode needs to be installed.\n", + gamemode_error_string()); + } + return false; + } + + if (on) + { + if (gamemode_status != 2 && gamemode_request_start() != 0) + { + RARCH_WARN("[GameMode]: Failed to enter GameMode: %s.\n", gamemode_error_string()); + return false; + } + } + else + { + if (gamemode_status == 2 && gamemode_request_end() != 0) + { + RARCH_WARN("[GameMode]: Failed to exit GameMode: %s.\n", gamemode_error_string()); + return false; + } + } + return true; +#else + (void)on; +#endif +} + static void frontend_unix_deinit(void *data) { settings_t *settings = config_get_ptr(); @@ -2006,6 +2048,8 @@ static void frontend_unix_deinit(void *data) if (settings->uints.screen_brightness != DEFAULT_SCREEN_BRIGHTNESS) frontend_unix_set_screen_brightness(DEFAULT_SCREEN_BRIGHTNESS); #endif + + frontend_unix_set_gamemode(false); } static void frontend_unix_init(void *data) @@ -2853,6 +2897,11 @@ frontend_ctx_driver_t frontend_ctx_unix = { NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ #endif +#ifdef FERAL_GAMEMODE + frontend_unix_set_gamemode, +#else + NULL, +#endif #ifdef ANDROID "android", /* ident */ #else diff --git a/frontend/drivers/platform_uwp.c b/frontend/drivers/platform_uwp.c index 0ad61f3d55..44a30c4391 100644 --- a/frontend/drivers/platform_uwp.c +++ b/frontend/drivers/platform_uwp.c @@ -479,6 +479,7 @@ frontend_ctx_driver_t frontend_ctx_uwp = { frontend_uwp_get_user_language, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "uwp", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_wiiu.c b/frontend/drivers/platform_wiiu.c index 03e028a872..e0c87c56a4 100644 --- a/frontend/drivers/platform_wiiu.c +++ b/frontend/drivers/platform_wiiu.c @@ -328,6 +328,7 @@ frontend_ctx_driver_t frontend_ctx_wiiu = NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "wiiu", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_win32.c b/frontend/drivers/platform_win32.c index bb351be7bc..91168a34bb 100644 --- a/frontend/drivers/platform_win32.c +++ b/frontend/drivers/platform_win32.c @@ -1170,6 +1170,7 @@ frontend_ctx_driver_t frontend_ctx_win32 = { NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ #endif + NULL, /* set_gamemode */ "win32", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_xdk.c b/frontend/drivers/platform_xdk.c index 4f52570013..541b5d471e 100644 --- a/frontend/drivers/platform_xdk.c +++ b/frontend/drivers/platform_xdk.c @@ -445,6 +445,7 @@ frontend_ctx_driver_t frontend_ctx_xdk = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "xdk", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/drivers/platform_xenon.c b/frontend/drivers/platform_xenon.c index ef473d9a3d..6777da6f70 100644 --- a/frontend/drivers/platform_xenon.c +++ b/frontend/drivers/platform_xenon.c @@ -100,6 +100,7 @@ frontend_ctx_driver_t frontend_ctx_qnx = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "xenon", /* ident */ NULL /* get_video_driver */ }; diff --git a/frontend/frontend_driver.c b/frontend/frontend_driver.c index efe819513a..4d1850dad3 100644 --- a/frontend/frontend_driver.c +++ b/frontend/frontend_driver.c @@ -71,6 +71,7 @@ static frontend_ctx_driver_t frontend_ctx_null = { NULL, /* get_user_language */ NULL, /* is_narrator_running */ NULL, /* accessibility_speak */ + NULL, /* set_gamemode */ "null", NULL, /* get_video_driver */ }; @@ -588,3 +589,12 @@ enum retro_language frontend_driver_get_user_language(void) return frontend->get_user_language(); return RETRO_LANGUAGE_ENGLISH; } + +bool frontend_driver_set_gamemode(bool on) +{ + frontend_state_t *frontend_st = &frontend_driver_st; + frontend_ctx_driver_t *frontend = frontend_st->current_frontend_ctx; + if (frontend && frontend->set_gamemode) + return frontend->set_gamemode(on); + return false; +} diff --git a/frontend/frontend_driver.h b/frontend/frontend_driver.h index 1cfdc82cbe..dd9a10fb5d 100644 --- a/frontend/frontend_driver.h +++ b/frontend/frontend_driver.h @@ -113,6 +113,7 @@ typedef struct frontend_ctx_driver bool (*is_narrator_running)(void); bool (*accessibility_speak)(int speed, const char* speak_text, int priority); + bool (*set_gamemode)(bool on); const char *ident; @@ -231,6 +232,8 @@ const char* frontend_driver_get_cpu_model_name(void); enum retro_language frontend_driver_get_user_language(void); +bool frontend_driver_set_gamemode(bool on); + frontend_state_t *frontend_state_get_ptr(void); RETRO_END_DECLS diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index f0b5ea5451..1b55ab7d10 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -5144,3 +5144,7 @@ MSG_HASH( MENU_ENUM_LABEL_MIXER_ACTION_VOLUME, "mixer_action_volume" ) +MSG_HASH( + MENU_ENUM_LABEL_GAMEMODE_ENABLE, + "game_mode_enable" + ) diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 75d5483731..f34f26236f 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -2374,6 +2374,19 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) snprintf(s, len, "Sets the master volume of the output device."); break; +#ifdef __linux__ + case MENU_ENUM_LABEL_GAMEMODE_ENABLE: + snprintf(s, len, + "Enabling Linux GameMode can improve latency, fix audio\n" + "crackling issues and maximize overall performance by\n" + "automatically configuring your CPU and GPU for best\n" + "performance.\n" + " \n" + "The GameMode software needs to be installed for this to\n" + "work. See https://github.com/FeralInteractive/gamemode for\n" + "information on how to install GameMode."); + break; +#endif default: if (string_is_empty(s)) strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE), len); diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 2dd83e74f7..d0985b689b 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -12595,6 +12595,14 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_CPU_POLICY_MENU_GOVERNOR, "Menu Governor" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_GAMEMODE_ENABLE, + "Game Mode" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_GAMEMODE_ENABLE_LINUX, + "Can improve performance, reduce latency and fix audio crackling issues. You need https://github.com/FeralInteractive/gamemode for this to work." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_PAL60_ENABLE, "Use PAL60 Mode" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 3c92d18897..b5bf0d65e6 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -943,6 +943,12 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_switch_cpu_profile, MENU DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_switch_gpu_profile, MENU_ENUM_SUBLABEL_SWITCH_GPU_PROFILE) #endif +#ifdef __linux__ +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_gamemode_enable, MENU_ENUM_SUBLABEL_GAMEMODE_ENABLE_LINUX) +#else +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_gamemode_enable, MENU_ENUM_SUBLABEL_GAMEMODE_ENABLE) +#endif + DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_brightness_control, MENU_ENUM_SUBLABEL_BRIGHTNESS_CONTROL) #if defined(_3DS) @@ -4292,6 +4298,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_switch_gpu_profile); break; #endif + case MENU_ENUM_LABEL_GAMEMODE_ENABLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_gamemode_enable); + break; case MENU_ENUM_LABEL_BRIGHTNESS_CONTROL: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_brightness_control); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 982db8df89..4a38f1484c 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8508,6 +8508,10 @@ unsigned menu_displaylist_build_list( false) == 0) count++; } + + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_GAMEMODE_ENABLE, PARSE_ONLY_BOOL, false) == 0) + count++; } break; case DISPLAYLIST_ONSCREEN_NOTIFICATIONS_SETTINGS_LIST: @@ -8936,6 +8940,7 @@ unsigned menu_displaylist_build_list( menu_displaylist_build_info_t build_list[] = { {MENU_ENUM_LABEL_SUSTAINED_PERFORMANCE_MODE, PARSE_ONLY_BOOL}, {MENU_ENUM_LABEL_CPU_PERFPOWER, PARSE_ACTION}, + {MENU_ENUM_LABEL_GAMEMODE_ENABLE, PARSE_ONLY_BOOL}, }; for (i = 0; i < ARRAY_SIZE(build_list); i++) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 21d8eb8274..6ba295024c 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -7594,6 +7594,9 @@ static void general_write_handler(rarch_setting_t *setting) task_queue_unset_threaded(); } break; + case MENU_ENUM_LABEL_GAMEMODE_ENABLE: + frontend_driver_set_gamemode(config_get_ptr()->bools.gamemode_enable); + break; case MENU_ENUM_LABEL_INPUT_POLL_TYPE_BEHAVIOR: core_set_poll_type(*setting->value.target.integer); break; @@ -17273,6 +17276,22 @@ static bool setting_append_list( #endif #endif + if (frontend_get_ptr()->set_gamemode) + CONFIG_BOOL( + list, list_info, + &settings->bools.gamemode_enable, + MENU_ENUM_LABEL_GAMEMODE_ENABLE, + MENU_ENUM_LABEL_VALUE_GAMEMODE_ENABLE, + DEFAULT_GAMEMODE_ENABLE, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + END_SUB_GROUP(list, list_info, parent_group); END_GROUP(list, list_info, parent_group); break; diff --git a/msg_hash.h b/msg_hash.h index 74c7bb8b74..c75653ba89 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -3037,6 +3037,7 @@ enum msg_hash_enums MENU_LABEL(MIDI_OUTPUT), MENU_LABEL(MIDI_VOLUME), + /* Power Management */ MENU_LABEL(SUSTAINED_PERFORMANCE_MODE), MENU_LABEL(CPU_PERF_MODE), MENU_LABEL(CPU_PERFPOWER), @@ -3048,6 +3049,8 @@ enum msg_hash_enums MENU_LABEL(CPU_POLICY_MENU_GOVERNOR), MENU_LABEL(CPU_MANAGED_MIN_FREQ), MENU_LABEL(CPU_MANAGED_MAX_FREQ), + MENU_LABEL(GAMEMODE_ENABLE), + MENU_ENUM_SUBLABEL_GAMEMODE_ENABLE_LINUX, MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF, MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PER_CONTEXT,