diff options
| author | xcfrg <30675315+xcfrg@users.noreply.github.com> | 2023-07-16 18:45:33 -0400 | 
|---|---|---|
| committer | flodavid <fl.david.53@gmail.com> | 2023-11-25 19:30:29 +0100 | 
| commit | dfa56765d6d869a317ec46dcf3a8f4f35b146382 (patch) | |
| tree | 827c46ada3f60e9bdef93c25a26319f00377d15f | |
| parent | 5a182f4e7ccfac696cd54542089c880d002f5cc9 (diff) | |
yuzu: integrate gamemode support on linux
| -rw-r--r-- | externals/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | externals/gamemode/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | externals/gamemode/include/gamemode_client.h | 379 | ||||
| -rw-r--r-- | src/common/settings.h | 2 | ||||
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_general.cpp | 3 | ||||
| -rw-r--r-- | src/yuzu/configuration/shared_translation.cpp | 1 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 54 | ||||
| -rw-r--r-- | src/yuzu/uisettings.h | 3 | ||||
| -rw-r--r-- | src/yuzu_cmd/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/yuzu_cmd/yuzu.cpp | 24 | 
11 files changed, 481 insertions, 1 deletions
| diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 515e3f2a4..36fc60e0e 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 000000000..3dddc6dbd --- /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 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 <stdbool.h> +#include <stdio.h> + +#include <dlfcn.h> +#include <string.h> + +#include <assert.h> + +#include <sys/types.h> + +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 e75099b89..788020bde 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -178,6 +178,8 @@ struct Values {                                               true,                                               &use_speed_limit}; +    Setting<bool> enable_gamemode{linkage, false, "enable_gamemode", Category::Core}; +      // Cpu      SwitchableSetting<CpuAccuracy, true> cpu_accuracy{linkage,           CpuAccuracy::Auto,                                                        CpuAccuracy::Auto, CpuAccuracy::Paranoid, diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 90278052a..f3ad2214b 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 c727fadd1..ce7e17850 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 a7b5def32..903805e75 100644 --- a/src/yuzu/configuration/shared_translation.cpp +++ b/src/yuzu/configuration/shared_translation.cpp @@ -175,6 +175,7 @@ std::unique_ptr<TranslationMap> 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 defe45198..cf61d4258 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -185,6 +185,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;  }  #endif +#ifdef __linux__ +#include <gamemode_client.h> +#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 549a39e1b..3e5ddc07a 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h @@ -140,6 +140,9 @@ struct Values {                                        Settings::Specialization::Default,                                        true,                                        true}; +    // Gamemode +    Setting<bool> enable_gamemode{linkage, false, "enable_gamemode", Category::UiGeneral}; +      Setting<bool> 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 fbeba8813..002f3e841 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 0416d5951..1c3a1809b 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 <gamemode_client.h> +#endif +  static void PrintHelp(const char* argv0) {      std::cout << "Usage: " << argv0                << " [options] <filename>\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;  } | 
