diff options
| -rw-r--r-- | CMakeLists.txt | 1 | ||||
| -rw-r--r-- | launcher/Application.cpp | 5 | ||||
| -rw-r--r-- | launcher/BaseInstance.h | 1 | ||||
| -rw-r--r-- | launcher/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | launcher/NullInstance.h | 4 | ||||
| -rw-r--r-- | launcher/minecraft/MinecraftInstance.cpp | 36 | ||||
| -rw-r--r-- | launcher/minecraft/MinecraftInstance.h | 1 | ||||
| -rw-r--r-- | launcher/minecraft/launch/DirectJavaLaunch.cpp | 22 | ||||
| -rw-r--r-- | launcher/minecraft/launch/LauncherPartLaunch.cpp | 17 | ||||
| -rw-r--r-- | launcher/ui/pages/global/MinecraftPage.cpp | 13 | ||||
| -rw-r--r-- | launcher/ui/pages/global/MinecraftPage.ui | 60 | ||||
| -rw-r--r-- | launcher/ui/pages/instance/InstanceSettingsPage.cpp | 26 | ||||
| -rw-r--r-- | launcher/ui/pages/instance/InstanceSettingsPage.ui | 68 | ||||
| -rw-r--r-- | libraries/README.md | 8 | ||||
| -rw-r--r-- | libraries/gamemode/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | libraries/gamemode/include/gamemode_client.h | 365 | 
16 files changed, 628 insertions, 13 deletions
| diff --git a/CMakeLists.txt b/CMakeLists.txt index b09e7fd2..dd6918d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -293,6 +293,7 @@ add_subdirectory(libraries/classparser) # class parser library  add_subdirectory(libraries/optional-bare)  add_subdirectory(libraries/tomlc99) # toml parser  add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much +add_subdirectory(libraries/gamemode)  ############################### Built Artifacts ############################### diff --git a/launcher/Application.cpp b/launcher/Application.cpp index bafb928b..757d852f 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -638,6 +638,11 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)          m_settings->registerSetting("UseNativeOpenAL", false);          m_settings->registerSetting("UseNativeGLFW", false); +        // Peformance related options +        m_settings->registerSetting("EnableFeralGamemode", false); +        m_settings->registerSetting("EnableMangoHud", false); +        m_settings->registerSetting("UseDiscreteGpu", false); +          // Game time          m_settings->registerSetting("ShowGameTime", true);          m_settings->registerSetting("ShowGlobalGameTime", true); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 66177614..2a94dcc6 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -188,6 +188,7 @@ public:       * Create envrironment variables for running the instance       */      virtual QProcessEnvironment createEnvironment() = 0; +    virtual QProcessEnvironment createLaunchEnvironment() = 0;      /*!       * Returns a matcher that can maps relative paths within the instance to whether they are 'log files' diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b8db803b..f837ba7c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -998,6 +998,13 @@ target_link_libraries(Launcher_logic      BuildConfig      Katabasis  ) + +if (UNIX AND NOT CYGWIN AND NOT APPLE) +    target_link_libraries(Launcher_logic +        gamemode +    ) +endif() +  target_link_libraries(Launcher_logic      Qt5::Core      Qt5::Xml diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index ed421433..9b0a9331 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -39,6 +39,10 @@ public:      {          return QProcessEnvironment();      } +    QProcessEnvironment createLaunchEnvironment() override +    { +        return QProcessEnvironment(); +    }      QMap<QString, QString> getVariables() const override      {          return QMap<QString, QString>(); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 7e72601f..10f04e5b 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -154,6 +154,12 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO      m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);      m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); +    // Peformance related options +    auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); +    m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride); +    m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride); +    m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride); +      // Game time      auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);      m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); @@ -435,6 +441,36 @@ QProcessEnvironment MinecraftInstance::createEnvironment()      return env;  } +QProcessEnvironment MinecraftInstance::createLaunchEnvironment() +{ +    // prepare the process environment +    QProcessEnvironment env = createEnvironment(); + +#ifdef Q_OS_LINUX +    if (settings()->get("EnableMangoHud").toBool()) +    { +        auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so"; +        auto lib_path = env.value("LD_LIBRARY_PATH", "") +  ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/"; + +        env.insert("LD_PRELOAD", preload); +        env.insert("LD_LIBRARY_PATH", lib_path); +        env.insert("MANGOHUD", "1"); +    } + +    if (settings()->get("UseDiscreteGpu").toBool()) +    { +        // Open Source Drivers +        env.insert("DRI_PRIME", "1"); +        // Proprietary Nvidia Drivers +        env.insert("__NV_PRIME_RENDER_OFFLOAD", "1"); +        env.insert("__VK_LAYER_NV_optimus", "NVIDIA_only"); +        env.insert("__GLX_VENDOR_LIBRARY_NAME", "nvidia"); +    } +#endif + +    return env; +} +  static QString replaceTokensIn(QString text, QMap<QString, QString> with)  {      QString result; diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index fda58aa7..05450d41 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -91,6 +91,7 @@ public:      /// create an environment for launching processes      QProcessEnvironment createEnvironment() override; +    QProcessEnvironment createLaunchEnvironment() override;      /// guess log level from a line of minecraft log      MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override; diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp index 742170fa..152485b3 100644 --- a/launcher/minecraft/launch/DirectJavaLaunch.cpp +++ b/launcher/minecraft/launch/DirectJavaLaunch.cpp @@ -12,13 +12,18 @@   * See the License for the specific language governing permissions and   * limitations under the License.   */ -  #include "DirectJavaLaunch.h" + +#include <QStandardPaths> +  #include <launch/LaunchTask.h>  #include <minecraft/MinecraftInstance.h>  #include <FileSystem.h>  #include <Commandline.h> -#include <QStandardPaths> + +#ifdef Q_OS_LINUX +#include "gamemode_client.h" +#endif  DirectJavaLaunch::DirectJavaLaunch(LaunchTask *parent) : LaunchStep(parent)  { @@ -50,7 +55,7 @@ void DirectJavaLaunch::executeTask()      auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); -    m_process.setProcessEnvironment(instance->createEnvironment()); +    m_process.setProcessEnvironment(instance->createLaunchEnvironment());      // make detachable - this will keep the process running even if the object is destroyed      m_process.setDetachable(true); @@ -79,6 +84,17 @@ void DirectJavaLaunch::executeTask()      {          m_process.start(javaPath, args);      } + +#ifdef Q_OS_LINUX +    if (instance->settings()->get("EnableFeralGamemode").toBool()) +    { +        auto pid = m_process.processId(); +        if (pid) +        { +            gamemode_request_start_for(pid); +        } +    } +#endif  }  void DirectJavaLaunch::on_state(LoggedProcess::State state) diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 427bc32b..e37c64fa 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -44,6 +44,10 @@  #include "Commandline.h"  #include "Application.h" +#ifdef Q_OS_LINUX +#include "gamemode_client.h" +#endif +  LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent)  {      auto instance = parent->instance(); @@ -102,7 +106,7 @@ void LauncherPartLaunch::executeTask()      auto javaPath = FS::ResolveExecutable(instance->settings()->get("JavaPath").toString()); -    m_process.setProcessEnvironment(instance->createEnvironment()); +    m_process.setProcessEnvironment(instance->createLaunchEnvironment());      // make detachable - this will keep the process running even if the object is destroyed      m_process.setDetachable(true); @@ -167,6 +171,17 @@ void LauncherPartLaunch::executeTask()      {          m_process.start(javaPath, args);      } + +#ifdef Q_OS_LINUX +    if (instance->settings()->get("EnableFeralGamemode").toBool()) +    { +        auto pid = m_process.processId(); +        if (pid) +        { +            gamemode_request_start_for(pid); +        } +    } +#endif  }  void LauncherPartLaunch::on_state(LoggedProcess::State state) diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index f49f5a92..e3ac7e7c 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -87,6 +87,11 @@ void MinecraftPage::applySettings()      s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());      s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked()); +    // Peformance related options +    s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); +    s->set("EnableMangoHud", ui->enableMangoHud->isChecked()); +    s->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); +      // Game time      s->set("ShowGameTime", ui->showGameTime->isChecked());      s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); @@ -109,6 +114,14 @@ void MinecraftPage::loadSettings()      ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());      ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool()); +    ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool()); +    ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); +    ui->useDiscreteGpuCheck->setChecked(s->get("UseDiscreteGpu").toBool()); + +#if !defined(Q_OS_LINUX) +    ui->perfomanceGroupBox->setVisible(false); +#endif +      ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());      ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());      ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui index 353390bd..640f436d 100644 --- a/launcher/ui/pages/global/MinecraftPage.ui +++ b/launcher/ui/pages/global/MinecraftPage.ui @@ -135,6 +135,45 @@          </widget>         </item>         <item> +        <widget class="QGroupBox" name="perfomanceGroupBox"> +         <property name="title"> +          <string>Performance</string> +         </property> +         <layout class="QVBoxLayout" name="verticalLayout_2"> +          <item> +           <widget class="QCheckBox" name="enableFeralGamemodeCheck"> +            <property name="toolTip"> +             <string><html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html></string> +            </property> +            <property name="text"> +             <string>Enable Feral GameMode</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QCheckBox" name="enableMangoHud"> +            <property name="toolTip"> +             <string><html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html></string> +            </property> +            <property name="text"> +             <string>Enable MangoHud</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QCheckBox" name="useDiscreteGpuCheck"> +            <property name="toolTip"> +             <string><html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html></string> +            </property> +            <property name="text"> +             <string>Use discrete GPU</string> +            </property> +           </widget> +          </item> +         </layout> +        </widget> +       </item> +       <item>          <widget class="QGroupBox" name="gameTimeGroupBox">           <property name="title">            <string>Game time</string> @@ -181,15 +220,15 @@             </widget>            </item>            <item> -            <widget class="QCheckBox" name="quitAfterGameStopCheck"> -             <property name="toolTip"> -              <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> -             </property> -             <property name="text"> -              <string>&Quit the launcher after game window closes</string> -             </property> -            </widget> -           </item> +           <widget class="QCheckBox" name="quitAfterGameStopCheck"> +            <property name="toolTip"> +             <string><html><head/><body><p>The launcher will automatically quit after the game exits or crashes.</p></body></html></string> +            </property> +            <property name="text"> +             <string>&Quit the launcher after game window closes</string> +            </property> +           </widget> +          </item>           </layout>          </widget>         </item> @@ -218,6 +257,9 @@    <tabstop>windowHeightSpinBox</tabstop>    <tabstop>useNativeGLFWCheck</tabstop>    <tabstop>useNativeOpenALCheck</tabstop> +  <tabstop>enableFeralGamemodeCheck</tabstop> +  <tabstop>enableMangoHud</tabstop> +  <tabstop>useDiscreteGpuCheck</tabstop>   </tabstops>   <resources/>   <connections/> diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index b4562843..459447c8 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -232,6 +232,22 @@ void InstanceSettingsPage::applySettings()          m_settings->reset("UseNativeGLFW");      } +    // Performance +    bool performance = ui->perfomanceGroupBox->isChecked(); +    m_settings->set("OverridePerformance", performance); +    if(performance) +    { +        m_settings->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); +        m_settings->set("EnableMangoHud", ui->enableMangoHud->isChecked()); +        m_settings->set("UseDiscreteGpu", ui->useDiscreteGpuCheck->isChecked()); +    } +    else +    { +        m_settings->reset("EnableFeralGamemode"); +        m_settings->reset("EnableMangoHud"); +        m_settings->reset("UseDiscreteGpu"); +    } +      // Game time      bool gameTime = ui->gameTimeGroupBox->isChecked();      m_settings->set("OverrideGameTime", gameTime); @@ -325,6 +341,16 @@ void InstanceSettingsPage::loadSettings()      ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());      ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool()); +    // Performance +    ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool()); +    ui->enableFeralGamemodeCheck->setChecked(m_settings->get("EnableFeralGamemode").toBool()); +    ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool()); +    ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); + +    #if !defined(Q_OS_LINUX) +    ui->perfomanceGroupBox->setVisible(false); +    #endif +      // Miscellanous      ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool());      ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool()); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index cb66b3ce..8b3c3370 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -455,6 +455,74 @@         </item>        </layout>       </widget> +     <widget class="QWidget" name="performancePage"> +      <attribute name="title"> +       <string>Performance</string> +      </attribute> +      <layout class="QVBoxLayout" name="verticalLayout_14"> +       <item> +        <widget class="QGroupBox" name="perfomanceGroupBox"> +         <property name="enabled"> +          <bool>true</bool> +         </property> +         <property name="title"> +          <string>Performance</string> +         </property> +         <property name="checkable"> +          <bool>true</bool> +         </property> +         <property name="checked"> +          <bool>false</bool> +         </property> +         <layout class="QVBoxLayout" name="verticalLayout_13"> +          <item> +           <widget class="QCheckBox" name="enableFeralGamemodeCheck"> +            <property name="toolTip"> +             <string><html><head/><body><p>Enable Feral Interactive's GameMode, to potentially improve gaming performance.</p></body></html></string> +            </property> +            <property name="text"> +             <string>Enable Feral GameMode</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QCheckBox" name="enableMangoHud"> +            <property name="toolTip"> +             <string><html><head/><body><p>Enable MangoHud's advanced performance overlay.</p></body></html></string> +            </property> +            <property name="text"> +             <string>Enable MangoHud</string> +            </property> +           </widget> +          </item> +          <item> +           <widget class="QCheckBox" name="useDiscreteGpuCheck"> +            <property name="toolTip"> +             <string><html><head/><body><p>Use the discrete GPU instead of the primary GPU.</p></body></html></string> +            </property> +            <property name="text"> +             <string>Use discrete GPU</string> +            </property> +           </widget> +          </item> +         </layout> +        </widget> +       </item> +       <item> +        <spacer name="verticalSpacer_2"> +         <property name="orientation"> +          <enum>Qt::Vertical</enum> +         </property> +         <property name="sizeHint" stdset="0"> +          <size> +           <width>20</width> +           <height>40</height> +          </size> +         </property> +        </spacer> +       </item> +      </layout> +     </widget>       <widget class="QWidget" name="miscellaneousPage">        <attribute name="title">         <string>Miscellaneous</string> diff --git a/libraries/README.md b/libraries/README.md index 7e7e740d..bdaef7a6 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -179,3 +179,11 @@ Licenced under the MIT licence.  Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.  Public domain. + +## gamemode + +performance optimisation daemon + +Upstream https://github.com/FeralInteractive/gamemode + +BSD licensed diff --git a/libraries/gamemode/CMakeLists.txt b/libraries/gamemode/CMakeLists.txt new file mode 100644 index 00000000..9e07f34a --- /dev/null +++ b/libraries/gamemode/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.9.4) +project(gamemode +    VERSION 1.6.1) + +add_library(gamemode) +target_include_directories(gamemode PUBLIC include) +target_link_libraries(gamemode PUBLIC ${CMAKE_DL_LIBS}) diff --git a/libraries/gamemode/include/gamemode_client.h b/libraries/gamemode/include/gamemode_client.h new file mode 100644 index 00000000..b6f7afd4 --- /dev/null +++ b/libraries/gamemode/include/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 <stdbool.h> +#include <stdio.h> + +#include <dlfcn.h> +#include <string.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; +	} + +	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 | 
