aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--launcher/Application.cpp5
-rw-r--r--launcher/BaseInstance.h1
-rw-r--r--launcher/CMakeLists.txt7
-rw-r--r--launcher/NullInstance.h4
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp36
-rw-r--r--launcher/minecraft/MinecraftInstance.h1
-rw-r--r--launcher/minecraft/launch/DirectJavaLaunch.cpp22
-rw-r--r--launcher/minecraft/launch/LauncherPartLaunch.cpp17
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp13
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui60
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp26
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui68
-rw-r--r--libraries/README.md9
-rw-r--r--libraries/gamemode/CMakeLists.txt7
-rw-r--r--libraries/gamemode/include/gamemode_client.h365
16 files changed, 629 insertions, 13 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5c5144f6..796374b8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -299,6 +299,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 dff5cb67..eb5a68cd 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -972,6 +972,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 88b1f7f3..889c6dde 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 fe8a1b1b..ea73ba60 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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable Feral GameMode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="enableMangoHud">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable MangoHud</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>&amp;Quit the launcher after game window closes</string>
- </property>
- </widget>
- </item>
+ <widget class="QCheckBox" name="quitAfterGameStopCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The launcher will automatically quit after the game exits or crashes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>&amp;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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable Feral Interactive's GameMode, to potentially improve gaming performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable Feral GameMode</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="enableMangoHud">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable MangoHud's advanced performance overlay.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="text">
+ <string>Enable MangoHud</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useDiscreteGpuCheck">
+ <property name="toolTip">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Use the discrete GPU instead of the primary GPU.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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 49879a26..360c34b1 100644
--- a/libraries/README.md
+++ b/libraries/README.md
@@ -9,6 +9,14 @@ This library has served as a base for some (much more full-featured and advanced
Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license.
+## gamemode
+
+A performance optimization daemon.
+
+See [github repo](https://github.com/FeralInteractive/gamemode).
+
+BSD licensed
+
## hoedown
Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté.
@@ -179,3 +187,4 @@ Licenced under the MIT licence.
Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth.
Public domain.
+
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