Compare commits

...

380 Commits
1.3 ... master

Author SHA1 Message Date
Ehren Bendler
af07e169d5 fix static analyzer error that is blocking CI 2025-05-02 08:30:35 +01:00
Ehren Bendler
6e5c950141 fix clang-format error that is blocking CI 2025-05-01 15:35:50 +01:00
MithicSpirit
499af4c7bb Prevent platform profile error on unsupported systems
If a system does not support setting the platform profile (i.e., does
not have the file /sys/firmware/acpi/platform_profile), then everything
that interacts with it is skipped to prevent errors. This situation is
more common than I expected.[1]

[1] https://github.com/FeralInteractive/gamemode/issues/524
2025-04-30 19:12:51 +01:00
Ehren Bendler
5f691c3171 Potential fix for code scanning alert no. 2: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-04-30 19:11:59 +01:00
Ehren Bendler
f5fbdcf014 Take advanted of imminent EOL of Ubuntu 20 to update GH runner config. Also make a pass over the development section of the README to reflect current reality. Update inih to r60. Update meson settings to eliminate deprecated functions. Set minimum Meson version to 1.3.1 to match Ubuntu 24 and OpenSUSE Leap 15.6. 2025-04-30 19:11:59 +01:00
Richard Martin
7c49d6e1db docs: a change was made to remove the supported games list, but it was still referenced from one location in the README.md 2025-03-14 10:23:06 +00:00
MithicSpirit
d84c5ded1c Extract split lock mitigation into common file
This reduces duplication of the split lock mitigation path.
Additionally, it allows extending the functionality of procsysctl into
also getting the split lock mitigation state.
2025-02-24 15:43:15 +00:00
MithicSpirit
2d71be6a94 Save all state before any of it is changed
The platform profile may restrict what values the governor can take, so
we need to save all state first to ensure the restored state is correct.
Then, the platform profile is the first thing we set (and restore) to
ensure all other changes are made to the best of our ability.
2025-02-24 15:43:15 +00:00
MithicSpirit
1e3e55c5d8 Add tests for platform profile
Note that the platform profile feature is considered required. Maybe
this shouldn't be the case, as I'm not sure how standard this feature is
yet.
2025-02-24 15:43:15 +00:00
MithicSpirit
e75f2c3942 Support setting the platform profile
The platform profile lives in /sys/firmware/acpi/platform_profile. The
desiredprof and defaultprof options correspond to the values for the
platform profile set when gamescope begins and ends, respectively.

HACK: The platform profile may restrict what values the governor can
take, so we choose to set the governor before the platform profile in
order to store the correct default governor, and restore the platform
profile before the governor. This is done to maximize correctness after
restoration, but it can cause issues if the previous platform profile
restricts the governor.

TODO: Save all the state we care about before any of it is changed. In
thsi case, we should set (and restore) the platform profile before the
governor.
2025-02-24 15:43:15 +00:00
Ahsan Fayaz
81d26c79b4 Remove list of supported apps
GameMode integration is widespread nowadays, and it works out of the box
for the most common use cases.

There is no longer a need for us to maintain a list of supported games
and applications, allowing us to streamline the readme file.
2025-02-24 15:15:17 +00:00
Ahsan Fayaz
5b3b4ae638 Add "GameMode contributors" to copyright notice
Attribute the contributions made by members of the community, but avoid
unweildy and inconsistent copyright notices at the top of each file.

Existing contributor copyright notices have been left as-is.
2025-02-24 15:14:21 +00:00
Henrik Holst
1d9c1e6a72
Use better kernel api for P/E-cores (#515)
There's a proper kernel way to check for the presence of P- and E-cores so we don't have to check the frequency differences.

Kept the frequency check in case the kernel way does not exist on the users kernel but upped the fail-safe to 10% from 5% to close #498
2025-01-27 08:25:32 +00:00
Kaydax
2eecd513ac Update README.md
PolyMC seems to have been removed for the reason "PolyMC died" yet the project is still active. Requesting this be added back as it still supports gamemode even on the latest version
2025-01-13 11:43:31 +00:00
afayaz-feral
086b3fe5b4
Update build-and-test.yml
Update to upload-artifact@v4
2024-11-06 11:54:08 +00:00
Andrew Koroluk
2f69f7c023 Add suggestion message to failed CPU governernor test 2024-11-04 11:04:05 +00:00
Gilles Schintgen
c54d6d4243 fix project version in meson.build (1.8.1->1.8.2) 2024-09-03 12:53:21 +01:00
Ahsan Fayaz
715a1389b7 Update version to 1.8.2 2024-08-19 11:18:51 +01:00
Ahsan Fayaz
c0516059ed Update copyright year 2024-08-09 13:14:27 +01:00
Ahsan Fayaz
a875008d8e Standardise formatting of "Other Apps" section 2024-08-09 13:12:16 +01:00
Mahmut Sacit Meydanal
ec10c591ff
README: Add new gamemode extension link (#484)
* README: Add new gamemode extension link

Since the old extension is no longer maintained by the original author and has stopped working in the latest releases of GNOME, I recommend including my extension in the README.
2024-08-09 11:59:49 +01:00
mhmarf
fef538ba92
Update gamemode.ini (#490)
Clarify that user must be added to the gamemode group in order to make Core Parking work.
2024-08-01 14:22:16 +01:00
peelz
a2fe0108b5 Drop duplicate clang-format rule 2024-03-27 16:44:45 +00:00
peelz
c854772369 Fix idle inhibitor closing bus connection too early 2024-03-27 16:44:45 +00:00
Kira Bruneau
7d55ef856b add homepage url to metainfo 2024-03-27 10:22:58 +00:00
Daniel Martinez
1c293d7818 use posix basename to fix build on musl
glibc provides a nonstandard basename implementation,
this can be overriden and posix basename can be used
by includeing libgen.h, however musl only has posix
basename, and must always include libgen.h

In this particular case, it doesn't appear that using
the posix version of basename will cause any issues,
as it is simply being used to match a hardcoded config
file name.
2024-03-27 10:17:17 +00:00
Reilly Brogan
9646f2bd93 gamemodelist: Fix unreadable process maps
Systemd commit [bf1b9ae487b65b1cb1639b222724fab95e508cf5](bf1b9ae487) (present in Systemd v254 and later) effectively broke gamemodelist as the process map for the `systemd --user` process became unreadable. After this change gamemodelist would exit with an error like the following:

```
awk: fatal: cannot open file `/proc/2281/maps' for reading: Permission denied
```

To work around this let's add a hook to the awk statement to skip any files that can't be read.

Closes FeralInteractive/gamemode#456
2024-03-27 10:16:28 +00:00
Jrelvas
8e0a71a0bc README: Add Vinegar to apps list
Vinegar is a bootstrapper for Roblox Player and Roblox Studio. It uses gamemode for both by default.
2024-02-14 10:37:00 +00:00
Trial97
3fa41891cf Fixed crash if dbus is not accesible
Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
2024-02-14 10:33:22 +00:00
patatahooligan
8cea4c2418 Fix hybrid CPU core pinning
Previously the condition would always evaluate to `false`. With the fix
any core with >=95% of `max_freq` will be added to the set of cores to
keep.
2024-01-03 11:09:13 +00:00
Kostadin Shishmanov
4a82094c98 Fix build with musl
Fixes the following build failure:
In file included from ../common/common-cpu.c:32:
../common/common-cpu.h:44:9: error: unknown type name 'cpu_set_t'
   44 |         cpu_set_t *online;
      |         ^~~~~~~~~
../common/common-cpu.h:45:9: error: unknown type name 'cpu_set_t'
   45 |         cpu_set_t *to_keep;
      |         ^~~~~~~~~

Signed-off-by: Kostadin Shishmanov <kocelfc@tutanota.com>
2024-01-03 10:54:40 +00:00
Ahsan Fayaz
5180d89e66 Update version to 1.8.1 2023-12-12 16:02:49 +00:00
Vamist
e82e9dafd0 [fix] polkit parse error caused by an extra ) 2023-12-11 09:14:05 +00:00
Ahsan Fayaz
bb40374067 Update version number in README.md 2023-12-06 17:11:07 +00:00
Ahsan Fayaz
c3b9d4ce8b Update changelog and version for 1.8 2023-12-06 17:06:52 +00:00
Ahsan Fayaz
070216ad81 Update copyright year 2023-12-06 17:06:51 +00:00
Ahsan Fayaz
6b03bc9354 Sort list of supported apps 2023-12-06 17:06:51 +00:00
Ahsan Fayaz
5867058d19 Add packagecache to .gitignore 2023-12-06 17:06:51 +00:00
Ahsan Fayaz
49dc7ee49b Add --reset flag to meson subprojects update
This prevents us from exiting early, as meson will return non zero.
2023-12-06 17:06:51 +00:00
Henrik Holst
41191dc607 Update gamemode-context.c
check format wined again, looks like I'm really bad at this
2023-12-05 11:31:16 +00:00
Henrik Holst
22001c56b1 Update gamemode-config.c
fixed format to match the proper codingstyle
2023-12-05 11:31:16 +00:00
Henrik Holst
088639e8b2 Update gamemode-context.c
fixed format to match the proper codingstyle
2023-12-05 11:31:16 +00:00
Henrik Holst
9e21ac3924 Update gamemode-context.c
fixed format to match the proper codingstyle
2023-12-05 11:31:16 +00:00
Henrik Holst
e2e8e981a2 Update gamemode-config.c
fixed format to match the proper codingstyle
2023-12-05 11:31:16 +00:00
Henrik Holst
7389d5ed1d Update procsysctl.c
fixed format to match the proper codingstyle
2023-12-05 11:31:16 +00:00
Henrik Holst
162374c026 Create procsysctl.c
added a new pkexec helper to modify values in /proc/sys, currently only can do writes to /proc/sys/kernel/split_lock_mitigate
2023-12-05 11:31:16 +00:00
Henrik Holst
4700089325 Update meson.build
added build info for the new helper to modify /proc/sys values
2023-12-05 11:31:16 +00:00
Henrik Holst
c7a4572d73 Update gamemode.ini
added info about and default value for the setting to enable and disable split lock mitigation
2023-12-05 11:31:16 +00:00
Henrik Holst
7381d93c13 Update gamemode.rules.in
added policykit rules for a new helper to modify values in /proc/sys
2023-12-05 11:31:16 +00:00
Henrik Holst
97e588a5ad Update com.feralinteractive.GameMode.policy.in
added plociykit support for a new helper to write to /proc/sys
2023-12-05 11:31:16 +00:00
Henrik Holst
9a7ee00bbf Update gamemode-context.c
added support for disabling the kernel split lock mitigation when entering game mode
2023-12-05 11:31:16 +00:00
Henrik Holst
b2bd7e5919 Update gamemode-config.h
added config values for disabling split lock mitigation
2023-12-05 11:31:16 +00:00
Henrik Holst
97cee92d94 Update gamemode-config.c
added config value for disabling split lock mitigation
2023-12-05 11:31:16 +00:00
Ahsan Fayaz
b9e8448afe Replace crypto mining reference with folding@home
Based on the change originally made by @joelsgp in PR #417, but there
were issues with the original commit message:
- Used an emoji which may cause rendering issues in CLI tools
- Mentioned Bitcoin mining which is inaccurate and contained
unnecessary censorship
2023-12-04 18:12:22 +00:00
Ahsan Fayaz
6c197f9b70 Apply clang-format with the new include order 2023-12-04 17:00:20 +00:00
Ahsan Fayaz
485a7f920a Move includes to the file that actually uses them
This was spotted thanks to clang-format reordering the includes.
Even with the new config, it will include the header file for the
current source file first. Nevertheless, it is best to always include
header files where they are needed.
2023-12-04 16:48:07 +00:00
Ahsan Fayaz
f48c58f34c Update include order in .clang-format
We generally want system headers included above project ones.
In one case <linux/limits.h> needs to be above other system headers.
2023-12-04 16:42:15 +00:00
Ahsan Fayaz
fad889db45 Apply clang-format to CPU pinning and parking code
The original PR #416 failed the format check, but this wasn't apparent
until after merging.
2023-12-04 15:33:15 +00:00
begin-theadventure
775c93001c [gamemode.ini] Add renice information about the user group. 2023-12-04 14:57:22 +00:00
wantija
160bf91665 Update README.md
Added game mode support for cemu (1bcdb35e42)
2023-12-04 14:54:05 +00:00
Hugo Locurcio
138ad384e3 Set the default GPU device ID to 0
This avoids confusion with the default configuration value being
`gpu_device=0` (as commented out in the `gamemode.ini` example),
but the actual default value being `-1`.

The alternative is to document this behavior and set the commented
out value to `-1`, but this makes the setup procedure more complex.
The new default behavior suits the 90% use case where there's
only one dedicated GPU available.
2023-12-04 14:21:32 +00:00
Henrik Holst
9614ceca9b pin every thread of the process
walk through /proc/pid/task to make sure that we set the thread affinity for every single thread in the process
2023-12-04 14:18:38 +00:00
Henrik Holst
865ffb2588 reapply the core pinning from the reaper thread
Reapply the core pinning from the reaper thread to catch cases where the game launches threads after initial start
2023-12-04 14:18:38 +00:00
Henrik Holst
b83fb8f83e made pinning optionally silent
made core pinning optionally silent, used for when the reaper thread calls us repeatable so we don't create tons of unnecessary logs
2023-12-04 14:18:38 +00:00
Henrik Holst
e882505881 properly support config reloading 2023-12-04 14:18:38 +00:00
Henrik Holst
88a15aba86 properly support config reloading 2023-12-04 14:18:38 +00:00
Henrik Holst
9cb119be62 properly support config reloading 2023-12-04 14:18:38 +00:00
Henrik Holst
fd226e46ba added info about the parking/pinning capability 2023-12-04 14:18:38 +00:00
Henrik Holst
91eb57574c fixed double free on exit from non use
if cpu core parking/pinning was disabled by the logic then there would be a double free at exit
2023-12-04 14:18:38 +00:00
Henrik Holst
25a99af4a1 use define instead of value
use a define instead of values for the park_or_pin variable
2023-12-04 14:18:38 +00:00
Henrik Holst
740a82a761 use defines instead of value
use defines instead of values for the park_or_pin variable
2023-12-04 14:18:38 +00:00
Henrik Holst
173a8594b4 fixed more coding style errors 2023-12-04 14:18:38 +00:00
Henrik Holst
81dc3f95e5 fixed more coding style errors 2023-12-04 14:18:38 +00:00
Henrik Holst
912d4bdc19 fixed more coding style errors 2023-12-04 14:18:38 +00:00
Henrik Holst
85b9abd654 fixed more coding style errors 2023-12-04 14:18:38 +00:00
Henrik Holst
2f7075b9c6 fix for clang-format 2023-12-04 14:18:38 +00:00
Henrik Holst
2e26331d97 fix to match clang-format 2023-12-04 14:18:38 +00:00
Henrik Holst
303a5a9734 fix to match clang-format 2023-12-04 14:18:38 +00:00
Henrik Holst
a9042199c6 fixed to match clang-format 2023-12-04 14:18:38 +00:00
Henrik Holst
3cabf9859d added info about suport for Intel E- and P-cores 2023-12-04 14:18:38 +00:00
Henrik Holst
51ee251efb Added detection for big.LITTLE
Added detection for big.LITTLE aka cpu:s where not all cores have the same frequency like on Intel Alder Lake and newer. The current logic allows a 5% difference in the max frequency due to some reports that those cpu:s doesn't always give back the exact same value (possible due to boosting capability).
2023-12-04 14:18:38 +00:00
Henrik Holst
ba1c593d0e added build info for cpucorectl
added build info for the new cpucorectl utility to meson
2023-12-04 14:18:38 +00:00
Henrik Holst
7d00c1f0a4 added cpucorectl.c
Added a utility to enable and disable cpu cores (aka core parking) since this requires root privileges
2023-12-04 14:18:38 +00:00
Henrik Holst
c070604a22 added cpu core parking/pinning settings to gamemode.ini
added some info about the new cpu core parking and pinning settings in gamemode.ini
2023-12-04 14:18:38 +00:00
Henrik Holst
dc2f7bbcd0 added polkit rules for cpucorectl
added polkit rules so we can run the cpucorectl utility without being root
2023-12-04 14:18:38 +00:00
Henrik Holst
437a129900 added polkit policy for cpucorectl
added the polkit policy for the cpucorectl utility so we can call it without being root
2023-12-04 14:18:38 +00:00
Henrik Holst
4997adef8d build gamemode-cpu
Added build info for gamemade-cpu.c to meson
2023-12-04 14:18:38 +00:00
Henrik Holst
1b10b679cd added the cpu core parking/pinning definitions
added the cpu core parking/pinning definitions to gamemode.h
2023-12-04 14:18:38 +00:00
Henrik Holst
495a659895 Added gamemode-cpu.c
Added gamemode-cpu.c which contains the functions for cpu core parking and pinning
2023-12-04 14:18:38 +00:00
Henrik Holst
2dbd565340 call the cpu core parking/pinning from context
call the cpu core parking/pinning from gamemode-context.c
2023-12-04 14:18:38 +00:00
Henrik Holst
01024927d2 added cpu core parking/pinning settings
added cpu core parking/pinning settings to gamemode-config.h
2023-12-04 14:18:38 +00:00
Henrik Holst
32a0b5b636 added the cpu core parking/pinning settings
added the cpu core parking/pinning settings to gamemode-config.c
2023-12-04 14:18:38 +00:00
Henrik Holst
520dc85ac3 updated meson.build to build common-cpu
added build info for common-cpu to meson
2023-12-04 14:18:38 +00:00
Henrik Holst
82206534dc Added common-cpu.c and common-cpu.h
Added common files for the cpu core parking/pinning functionality
2023-12-04 14:18:38 +00:00
Ahsan Fayaz
1f28880509 Update copyright year and project list
PolyMC died and most of the active devs created a fork, Prism Launcher
2023-05-23 11:37:01 +01:00
8lendzior
8cfc345312 issue #384, #399
I have fixed the issues by implementing this change before running the script
2023-03-02 11:27:44 +00:00
Hugo Locurcio
7cf59587ce Document some configuration files not allowing [gpu] changes
This also reorders the list from highest to lowest priority,
which is generally more commonly used.
2023-03-02 11:25:52 +00:00
MrAkells
f5882d5158 Update README.md 2023-03-02 11:25:25 +00:00
James Le Cuirot
06f01938a9 Fix typo in gamemodelist man page 2022-10-04 15:44:23 +01:00
James Le Cuirot
71f4b875ce Only install gamemoderun if shared libgamemodeauto is built
Otherwise emit a warning.
2022-10-04 15:44:23 +01:00
James Le Cuirot
b103bfdd60 Don't force installation of static libgamemodeauto
Defining the library with `library` rather than `both_libraries` allows the user
to choose which type they want to install using `-Ddefault_library`.

Closes: https://github.com/FeralInteractive/gamemode/issues/287
2022-10-04 15:44:23 +01:00
ashuntu
4cffdef805 Removed blank line 2022-09-14 17:57:49 +01:00
ashuntu
3f969bbf1f Fix env edgecase 2022-09-14 17:57:49 +01:00
ashuntu
1ca2daf47f Make more readable 2022-09-14 17:57:49 +01:00
ashuntu
179d5432e4 Add snap support 2022-09-14 17:57:49 +01:00
Nyikos Zoltán
4934191b19 Fix building when pidfd_open is available
On glibc2.36 pidfd_open was made available, but it needs an include
2022-08-12 15:54:57 +01:00
Kira Bruneau
55b799e3df Add option to specific PAM limits.d directory 2022-08-05 11:35:24 +01:00
Kira Bruneau
337f1b8a8e Scope polkit actions to require the privileged group 2022-08-05 11:35:24 +01:00
Kira Bruneau
898ab01924 Organize data files that hook into other packages 2022-08-05 11:35:24 +01:00
Kira Bruneau
1e24067430 Add option to specify privileged gamemode group 2022-08-05 11:35:24 +01:00
Kira Bruneau
e34e9c5a43 Add options to disable installing systemd specific files 2022-08-05 11:35:24 +01:00
Kira Bruneau
aee9703872 Add option to set systemd sysuser dir 2022-08-05 11:35:24 +01:00
Ahsan Fayaz
4dc99dff76 Specify correct inih directory after update to r54 2022-07-21 15:24:10 +01:00
Ahsan Fayaz
5fff74598a Use inih r54 from wrapdb
Updated using the command 'meson subprojects update'.
2022-07-21 15:21:25 +01:00
Ahsan Fayaz
c3d638d7ce Update version to 1.7 2022-07-21 15:03:58 +01:00
Ahsan Fayaz
86591acdf2 Update version references to 1.7 2022-07-21 15:03:34 +01:00
Ahsan Fayaz
618466005e Add changes for 1.7 2022-07-21 15:03:05 +01:00
Ahsan Fayaz
f5a715e124 Add recent Feral titles to the list of games 2022-07-20 11:50:53 +01:00
Ahsan Fayaz
f498f2a20a Update copyright year to 2022 2022-07-20 11:48:56 +01:00
Stephan Lachnit
9b44fc6def ci: fix static analyser with assertions
Signed-off-by: Stephan Lachnit <stephanlachnit@debian.org>
2022-07-15 14:17:38 +01:00
Stephan Lachnit
b58b32072a meson: fix warning in run_command
Fixes this warning:
```
WARNING: You should add the boolean check kwarg to the run_command call.
         It currently defaults to false,
         but it will default to true in future releases of meson.
         See also: https://github.com/mesonbuild/meson/issues/9300
```

Signed-off-by: Stephan Lachnit <stephanlachnit@debian.org>
2022-07-15 14:16:09 +01:00
Alfred Persson Forsberg
f50e7fffe7 Fix build on musl libc
This simple patch includes signal.h in daemon/gamemode-context.c to fix building gamemode on musl
libc.

This has been tested Gentoo musl and Alpine (also Gentoo glibc to
ensure no multiple defined symbols/other errors for glibc).

> ../daemon/gamemode-context.c: In function 'game_mode_context_auto_expire':
> ../daemon/gamemode-context.c:421:29: error: implicit declaration of function 'kill' [-Werror=implicit-function-declaration]
>   421 |                         if (kill(client->pid, 0) != 0) {
>       |                             ^~~~
> ../daemon/gamemode-context.c:421:29: warning: nested extern declaration of 'kill' [-Wnested-externs]

Signed-off-by: Alfred Persson Forsberg <cat@catcream.org>
2022-07-15 14:14:16 +01:00
Issam Maghni
fac33baa09 Simplify the offload command
The POSIX env [1] supports multiple `name=value` operands.
So I merged them into one.

[1] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html
2022-07-15 14:13:09 +01:00
Gregory keenan
e7dcf8792d Update README.md
Minor update on language
2022-07-15 14:12:14 +01:00
Gregory keenan
db1a0e3ace Update README.md
Added a section in the Build and Install section to test if its working correctly.
2022-07-15 14:12:14 +01:00
gouchi
01b849a518 README.md: add RetroArch to other apps section
[Source](https://github.com/libretro/RetroArch/pull/13339)
2022-07-15 14:11:23 +01:00
Sam Gleske
b8fa857b35 Fix GitHub Actions Static Analyzer Check
Dockerfile
----------

This dockerfile was used to emulate GitHub actions.

<details><summary>Dockerfile source (click to expand)</summary>

```dockerfile
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

RUN set -ex; \
apt-get update; \
apt-get install -y build-essential meson appstream clang clang-format clang-tools libdbus-1-dev libinih-dev libsystemd-dev git

RUN set -ex; \
yes | adduser ci-user

USER ci-user
```

</details>

Commands to reproduce
---------------------

I simulated GitHub actions with a local docker environment.

    docker build -t ci .

I ran the following commands to reproduce the issue so that I could bugfix the static analyzer script.

```bash
docker run -e CI=true --rm -v "$PWD:$PWD" -w "$PWD" --init ci ./scripts/static-analyser-check.sh
docker run -e CI=true --rm -v "$PWD:$PWD" -w "$PWD" --init ci ./bootstrap.sh -Dwith-examples=true
docker run --rm -v "$PWD:$PWD" -w "$PWD" --init ci meson test -C builddir
docker run --rm -v "$PWD:$PWD" -w "$PWD" --init ci dbus-run-session -- gamemode-simulate-game
docker run --rm -v "$PWD:$PWD" -w "$PWD" --init ci ./scripts/static-analyser-check.sh
```

All commands reuse the same workspace which is how it works in GH actions.

Legitimate bugs
---------------

Now the error displayed in build failure appears to be a legitimate bug.  Four were found related to null pointer dereference.  I'm not fixing the bug but the check should work properly for those who do.

If you following my docker setup in this description you can view the bug report by starting the report server.

```bash
docker run -p 127.0.0.1:8181:8181 -it --rm -v "$PWD:$PWD" -w "$PWD" --init ci scan-view --host 0.0.0.0 --allow-all-hosts builddir/meson-logs/scanbuild/*
```

After the report server is started you can visit `http://127.0.0.1:8181/` to view the bugs.

Screenshot of report
--------------------

![Screenshot of bug report](https://user-images.githubusercontent.com/875669/155830028-473db1ea-7c98-4a82-bc3a-290d5c155108.png)
2022-03-10 10:40:45 +00:00
Sam Gleske
23493e5cc3 Fix GitHub Actions Format Check
This change fixes the format check in GitHub actions ran by `clang-format`.

Dockerfile

```dockerfile
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive

RUN set -ex; \
apt-get update; \
apt-get install -y build-essential meson appstream clang clang-format clang-tools libdbus-1-dev libinih-dev libsystemd-dev git

RUN set -ex; \
yes | adduser ci-user

USER ci-user
```

Environment setup

```bash
sudo su -g docker $USER
docker build -t ci .
```

clang-format fix

```bash
docker run -e CI=true --rm -v "$PWD:$PWD" -w "$PWD" --init ci ./scripts/format-check.sh
```
2022-02-21 09:51:59 +00:00
Sam Gleske
c96f7379b4
New utility: gamemodelist (#346)
* New utility: gamemodelist

While trying out gamemode on Ubuntu 18.04 I had trouble figuring out
whether or not my games were running with gamemode enabled.  I wrote this
utility which prints all processes loaded with the gamemode shared
library.

- [x] Added utility to `data/` folder.
- [x] Update meson installer.
- [x] Included section 1 manual.
- [x] Updated README for Ubuntu 18.04 build instructions.  Steam supports
  Ubuntu 18.04.

I'm open to feedback and generally this should work for any distrobution
since it makes use of the Linux `/proc` filesystem.  [Learn more about
`/proc`][1].

[1]: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/Documentation/filesystems/proc.rst?h=v5.15.12
2022-02-21 09:50:57 +00:00
Emil Velikov
898feb9c52 data: add and install sysusers.d gamemode.conf
Add a trivial gamemode.conf file, which creates the gamemode group.

v2: git add gamemode.conf (d'oh)

Signed-off-by: Emil Velikov <emil.velikov@collabora.com>
2022-02-03 16:50:29 +00:00
Stephan Lachnit
ab06ba419e Switch to GitHub Actions
Signed-off-by: Stephan Lachnit <stephanlachnit@debian.org>
2022-02-03 16:40:02 +00:00
Stephan Lachnit
6c60565f33 Format files according to clang-format
Signed-off-by: Stephan Lachnit <stephanlachnit@debian.org>
2022-02-03 16:40:02 +00:00
Kira Bruneau
5e366bd55d Fix loading shipped config when using a prefix other than /usr
Also remove recommendation to modify `/usr/share/gamemode/gamemode.ini`.
`/usr/share` is intended to contain read-only data files.

See https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s11.html.
2022-02-03 16:02:18 +00:00
Kira Bruneau
126e67fb6b Run executables from PATH instead of /usr/bin
Not all distributions install non-system binaries into /usr/bin. For
example, NixOS installs packages to /nix/store using a unique hash
generated from the inputs used to build it:

/nix/store/jld7jh3ilvbg91zvn1bdyawfc55b9jk8-polkit-0.118-bin/bin/pkexec
2022-02-03 15:42:22 +00:00
Patrick Pulfer
4000a32584 Documentation: Add GCC dependency to Ubuntu installation guide 2021-05-24 11:27:27 +01:00
Ahsan Fayaz
b11d2912e2 Update inih version in CHANGELOG.md 2021-02-18 19:00:12 +00:00
Ahsan Fayaz
0344e7c311 Specify the correct inih subproject directory. 2021-02-18 18:52:59 +00:00
Ahsan Fayaz
993857d0e8 Use inih r53 from wrapdb. Updated using the command 'meson subprojects update'. 2021-02-18 18:42:08 +00:00
Ahsan Fayaz
5fd0065df6 Update version to 1.6.1 2021-02-18 18:39:53 +00:00
Ahsan Fayaz
6592c2229c Update version references to 1.6.1 2021-02-16 16:12:29 +00:00
Ahsan Fayaz
05a4c0c152 Add changes for 1.6.1 2021-02-16 16:11:59 +00:00
Ahsan Fayaz
fb7062bd9c Update copyright year to 2021. 2021-02-04 13:24:59 +00:00
Stephan Lachnit
49a0ef8c0b minor metainfo improvements
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-11-18 12:33:38 +00:00
Stephan Lachnit
cfa8b9a2c4 Use inih r52 from wrapdb
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-11-06 11:30:26 +00:00
Ivan Tham
62659edd2b Gamemode is now available on Arch community repo
https://www.archlinux.org/packages/community/x86_64/gamemode/
2020-09-30 09:09:06 +01:00
otreblan
9037b730f6 Add libinih pacman dependency 2020-09-30 09:08:50 +01:00
Ahsan Fayaz
5f71f57db1 Update CHANGELOG.md for 1.6. 2020-09-18 15:03:00 +01:00
Michael Butler
17b2b6201b Update README.md to point to release 1.6 in instructions 2020-09-18 14:56:14 +01:00
Rémi Verschelde
832f9ab7e0 Fix installation of man pages in proper man dirs
See https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch04s11.html#usrsharemanManualPages
2020-09-14 10:18:53 +01:00
sgleizes
5163c01d24 Allow LD_PRELOAD overrides from GAMEMODERUNEXEC commands 2020-09-01 16:21:05 +01:00
Niels Thykier
3fcff0d75f Align open_fdinfo_dir with pidfds_to_pids
The function `pidfds_to_pids` expected `-1` if `open_fdinfo_dir` failed but the latter returned `errno` which is hard to distinguish from a valid file handle.  Correct that by making `open_fdinfo_dir` a wrapper around `open`.
2020-08-12 09:41:28 +01:00
Ahsan Fayaz
510a0a6ae2 Fix issues found by Coverity, closes #206. 2020-07-17 18:55:00 +01:00
Stephan Lachnit
f6c68cd6de change location for the shipped default config
Before it was installed to /etc, but according the daemon/gamemode-config.c line 381 the shipped config should be in /usr/share/gamemode

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-07-09 08:14:09 +01:00
Stephan Lachnit
7a68a178ac simplify libgamemodeauto preloading
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-07-09 08:13:17 +01:00
Stephan Lachnit
8b408694b0 improve libgamemodeauto pkg-config file
Rename the pkg-config entry for libgamemodeauto, to it make it more that this will link against a library, compared to what the `gamemode` pkg-config does. This also removes the manual addition of the libdl dependency, this is done by Meson automatically.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-06-23 09:18:14 +01:00
Stephan Lachnit
d8337aeb05 various variable naming improvements
This makes it more clear that libgamemode and libgamemodeauto are indeed libraries. Also, the misleading name `libgamemode_dep` has been renamed to `gamemode_dep`, which now also includes the dependency on libdl. The misleading `libgamemode_includes` variable name has also been changed.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-06-23 09:18:14 +01:00
Stephan Lachnit
d4536c62af build gamemodeauto also as static library
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-06-23 09:18:14 +01:00
Stephan Lachnit
785df22274 travis: fix some config warnings
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-06-23 09:14:42 +01:00
Ahsan Hussain
8810e4f158 README.md: Update note for Nvidia Optimus proprietary driver users
Add a note for Nvidia hybrid graphics users, not having the opensource
nouveau driver, which can save them some time for searching the right
solution

Signed-off-by: Ahsan Hussain <ahsan_hussain@mentor.com>
2020-06-08 10:24:52 +01:00
RickAndTired
db7d52dbac Spelling / typo fix 2020-05-27 10:50:17 +01:00
Stephan Lachnit
3033867fc9 add man page for example game
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-27 10:49:36 +01:00
Stephan Lachnit
a48272ae61 fix incorrect name in gamemoderun man page
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-27 10:49:36 +01:00
Ahsan Fayaz
39f93e4000 Fix incorrect merge conflict resolution. 2020-05-26 17:43:23 +01:00
Stephan Lachnit
d27f8caecc improve path names for man pages and metainfo
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-26 12:53:21 +01:00
Stephan Lachnit
3d49f87308 configure version in man pages
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-26 12:53:21 +01:00
Ahsan Fayaz
d47dea92de Merge branch 'stephanlachnit-patch/mkrelease' 2020-05-26 12:50:10 +01:00
afayaz-feral
16ebc794c5 Merge branch 'master' into patch/mkrelease 2020-05-26 12:35:58 +01:00
Stephan Lachnit
082b1c6619 mkrelease.sh: get version via git
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-26 12:32:19 +01:00
Stephan Lachnit
937bcf3c73 remove gitignore
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-26 12:32:01 +01:00
Stephan Lachnit
e6355d91f4 simplify archive creation
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-20 18:05:37 +02:00
Stephan Lachnit
9ecff8d5d3 Meson: explicitly set include path
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-20 10:00:18 +01:00
Stephan Lachnit
9f676632c3 meson: ensure subprojects are up-to-date
... when creating the release tarball.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-19 16:35:00 +01:00
Stephan Lachnit
115f1ecdbd meson: use builddir instead of build
Use the -C option instead of changing the dir, change the build folder to builddir.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-19 16:35:00 +01:00
Stephan Lachnit
ce6485ef97 combine no-daemon, elogind and systemd option
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-19 16:27:51 +01:00
Stephan Lachnit
953792b4a5 Add option to use elogind
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-19 16:27:51 +01:00
Stephan Lachnit
1e8312f7e3 Add option to change lib dir in gamemoderun
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-13 10:27:15 +01:00
Stephan Lachnit
1709810707 Use distro provided git-clang-format
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-13 09:52:01 +01:00
Stephan Lachnit
c36019a9aa travis: run gamemoded -v as meson test
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
6453a123ab add metainfo test
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
f95470c94a travis: add libinih-dev as dependency
This will compile gamemode against the shared lib version of inih, as it should be in most distros.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
a1660fc37a add gamemode-simulate-game to travis
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
43911a8919 install example config
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
e1ae78e346 install example
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
f7a4a6ccfe expose dependency objects for libs
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
90d05eef42 travis: update to focal
This updates to Ubuntu 20.04 (focal) and uses it's provided Meson version.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
2fb62e34fa fix double spaces in example
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
c755f7e539 fix wrong prefix variable in data
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-12 19:25:57 +01:00
Stephan Lachnit
1ca9b727c3 remove some old example files
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-11 12:30:15 +01:00
Stephan Lachnit
329f7b4cee give gamemoderun an own man page
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-11 09:41:17 +01:00
Stephan Lachnit
695f7e565f Add return values to example
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-06 14:04:06 +01:00
Stephan Lachnit
e3c24f34f1 provide metainfo
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-06 12:34:06 +01:00
Anjune
29b4148a00 Make system revert to previous governor instead of "powersave"
See issue 214.
2020-05-06 11:32:10 +01:00
qarmin
94444cb76f Check index before using 2020-05-06 11:05:02 +01:00
Alan Jenkins
57bc3e26ba Add ATLauncher to gamemode compatible list
Adds ATLauncher to the list of applications that integrate with
Gamemode.
2020-05-06 11:04:29 +01:00
Alan Jenkins
0ba1551293 Sort the games list alphabetically in README
Sorts the games list alphabetically in the README.md.
2020-05-06 11:04:29 +01:00
Stephan Lachnit
094905f7f8 rename example to gamemode-simulate-game
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-06 11:02:29 +01:00
Ari Breitkreuz
c8492ca28f Fix typo in README and help message 2020-05-06 11:01:37 +01:00
Stephan Lachnit
faea358023 Use wrap instead of git submodule
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-05-06 11:01:13 +01:00
Ahsan Fayaz
bfa20975ca Add git-clang-format, copied from llvm-project GitHub repository. This is preferable to downloading it each time which often fails and causes errors with continuous integration. 2020-03-03 16:54:09 +00:00
Ahsan Fayaz
40702d3fed Update version back to 1.6 post-release 2020-03-03 16:35:37 +00:00
Ahsan Fayaz
616dca659a Update version to 1.5.1 2020-03-03 16:18:48 +00:00
Stephan Lachnit
d3e309b23b use upstream inih r48
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-03-03 14:38:35 +00:00
seb128
1a598f53d2 Don't default to syslog logging
Let the preferred/configured logging system to be used instead
2020-02-26 18:26:09 +00:00
Stephan Lachnit
f0943ff431 prefer system installation of inih
Closes #195.

Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2020-02-26 18:24:50 +00:00
Ahsan Fayaz
065454bb4e Update version to 1.6 post-release 2020-01-22 14:51:00 +00:00
Ahsan Fayaz
d58c59c183 Update version to 1.5 2020-01-22 14:19:26 +00:00
afayaz-feral
9d34caa1fa Merge pull request #186 from xcom169/patch-1
Add SotTR
2020-01-13 12:12:13 +00:00
xcom169
c2846f4a77 Add SotTR
Add SotTR game to gamemode.
2020-01-12 12:04:02 +01:00
Ahsan Fayaz
78b326adb6 Update copyright year to 2020. 2020-01-10 17:37:35 +00:00
afayaz-feral
1576c2b39e Merge pull request #179 from jekstrand/igpu
Add an option for using a different governor for integrated GPUs
2020-01-10 10:12:26 +00:00
Jason Ekstrand
79d0c64ef1 Add systemctl try-restart polkit to bootstrap.sh
This makes it more properly install things so that you don't get the
nasty pkexec pop-ups with dev builds.
2020-01-09 11:11:22 -06:00
Jason Ekstrand
688373a260 Add an option for using a different governor for integrated GPUs
This commit adds two new configuration options: igpu_desiredgov and
igpu_power_threshold which allow for a different CPU governor when the
Intel integrated GPU is under load.  This currently only applies to
Intel integrated GPUs and not AMD APUs because it uses the Intel RAPL
infrastructure for getting power information.  If on a platform that
without an Intel integrated GPU or where the kernel does not support
RAPL, the new options will be ignored and it will fall back to the old
behavior.

One of the core principals of gamemoded to date has been that, when
playing a game, we want to use the "performance" CPU governor to
increase CPU performance and prevent CPU-limiting.  However, when the
integrated GPU is under load, this can be counter-productive because the
CPU and GPU share a thermal and power budget.  By throwing the CPU
governor to "performance" game mode currently makes the CPU frequency
management far too aggressive and it burns more power than needed.  With
a discrete GPU, this is fine because the worst that happens is a bit
more fan noise.  With an integrated GPU, however, the additional power
being burned by the CPU is power not available to the GPU and this can
cause the GPU to clock down and lead to significantly worse performance.

By using the "powersave" governor instead of the "performance" governor
while the integrated GPU is under load, we can save power on the CPU
side which lets the GPU clock up higher.  On my Razer Blade Stealth 13
with an i7-1065G7, this improves the performance of "Shadow of the Tomb
Raider" by around 25-30% according to its internal benchmark mode.
2020-01-09 10:49:19 -06:00
Jason Ekstrand
c1646ecdd9 Add helpers for getting RAPL power data from Intel CPUs 2020-01-09 10:49:19 -06:00
Jason Ekstrand
2023a2a1ef Move the get/set governor state machine to a helper 2020-01-09 10:41:10 -06:00
Ahsan Fayaz
8b8113fb80 Stick to meson version 0.52.0. Version 0.53.0 has compatibility issues with python 3.5.2 which is shipped with Ubuntu 16.04. Better stick to a specific version and only update after checking compatibility. See mesonbuild/meson#6427. 2020-01-09 15:03:47 +00:00
Alex Smith
57efe440c3 clang-format fix 2019-10-22 12:41:37 +01:00
Alex Smith
b2b09fbb83 Run "sleep 5" instead of "sh" for the gamemoderun test
Don't really want to try to run an interactive shell here.
2019-10-22 12:36:59 +01:00
Alex Smith
d25379e001 Increase a test timeout to avoid spurious failures 2019-10-22 12:31:53 +01:00
Alex Smith
514ab58be3 Merge pull request #173 from gicmo/pidfds
Introduce a new pidfd based set of D-Bus APIs
2019-10-22 12:31:00 +01:00
Christian Kellner
6f7df91b60 lib: support the new pidfd based APIs
Try to make API requests using the new pidfd based APIs. If getting
the pidfds fails or if the remote (daemon) does not support the new
pidfd based D-Bus API, transparently fall back to the old API.
2019-10-21 16:20:22 +02:00
Christian Kellner
b84d673aae meson: reorder lib and common subdir
So that lib can depend on the new library from common.
2019-10-18 13:19:30 +02:00
Christian Kellner
cd6c2ee612 common: create another lib tailored for the client
Use a small subset of the existing common files to create another
static library to be used from the client library.
2019-10-18 13:19:30 +02:00
Christian Kellner
a6552044cd daemon: add new pidfd based D-Bus API
Provide a new set of APIs with identical semantics as the existing
ByPID family of calls but instead of working with process ids, they
take pidfds, file descriptors representing processes, instead. The
fds can be translated back to pids (in the correct namespace) and
also be monitored via select/poll/epoll.
The current implementation translates them directly back to pids,
but in the future the monitoring code that watches processes (if
they are still alive) could be converted be event driven via pidfds.
2019-10-18 13:19:30 +02:00
Christian Kellner
5398dd1d19 common: add pidfd related methods
Add functions to open pidfds, i.e. file descriptors representing
processes, for process ids and vice versa. Both functions work
an array of fds/pids, stop on error and return the number of
successfully handled items.
2019-10-18 13:19:30 +02:00
Christian Kellner
4b59818fd4 common: add autofree helper
Much like the auto-closing helper for file descriptors, add a new
auto-free helper that is meant to be used with dynamically allocated
memory, a la:
  autofree char *data = NULL;
  ...
  data = malloc(size);
When data goes out of scope, cleanup_free will be called with &data,
i.e. cleanup_free(&data), which in turn will call free(3) data. In
order to work with all types, e.g. 'char *' (resulting in char **
being passed to cleanup_free) or 'int *' (resulting in int ** being
passed to cleanup_free), cleanup_free is defined to work with void *,
hence the odd-looking cast: void *target = *(void **) ptr.
2019-10-18 13:19:30 +02:00
Christian Kellner
35fb7f5baf meson: include dir in daemon_common dependency
Specify the include directory in the link_daemon_common dependency
and thus everything that includes that as a dependency will get
the proper include directory automatically.
2019-10-18 13:19:30 +02:00
Christian Kellner
b7dc1dc10c daemon: small fix for code comments
Comments were not reflecting the what they were describing.
2019-10-18 13:19:30 +02:00
Christian Kellner
b513bc65ae lib: extract dbus messaging code
Separate the D-Bus messaging code from gamemode_request().
2019-10-18 13:19:30 +02:00
Christian Kellner
f6220a2d6e lib: do flatpak check only once
Either we are in a flatpak or not, this doesn't change, so we can
just remember the result.
2019-10-18 13:19:30 +02:00
Kai Krakow
24f054659c gamemode-context: Avoid GameModeClient *cl being NULL
There's no need in defining it at the top of the function. During
rebase, I had one `if` accessing `cl->executable` too early but we were
only storing the path in `executable` at that point.

This change avoids accessing `cl` while it might be NULL.

Signed-off-by: Kai Krakow <kai@kaishome.de>
2019-10-18 09:42:06 +01:00
Alex Smith
0668e3b4da Change Lutris instructions to say that it will enable GameMode by default when available 2019-08-12 08:40:53 +01:00
Alex Smith
30ae8d71f0 Adjust install prompt
Now reads: "Install to $prefix? [y/N]" to indicate that the default
choice if pressing enter without any extra input will be no. Suggested
by #169.
2019-08-11 10:14:46 +01:00
Alex Smith
509ae3593a Merge pull request #165 from Aurnytoraink/patch-1
Add Lutris instructions
2019-08-11 10:00:30 +01:00
Alex Smith
5add7f41b3 Merge pull request #168 from highvoltage/master
Fix typo s/Coudn't/Couldn't/g
2019-08-11 09:57:29 +01:00
Jonathan Carter
8182edc048 Fix typo s/Coudn't/Couldn't/g 2019-08-05 09:50:32 +00:00
Mathieu H
86473bea0a Add Lutris instructions 2019-07-29 16:41:07 +02:00
Alex Smith
711e5e9995 Merge pull request #163 from akien-mga/mageia-package
Mention Mageia package in README
2019-07-22 14:45:45 +01:00
Rémi Verschelde
33f8be9557 Mention Mageia package in README
http://madb.mageia.org/package/show/application/0/name/gamemode
2019-07-21 13:19:21 +02:00
Alex Smith
24687f960b Update version to 1.5-dev post-release 2019-07-21 10:39:11 +01:00
Alex Smith
acb57735fc Update version to 1.4 2019-07-21 10:24:18 +01:00
Christian Kellner
5c1b2d0c74 daemon: export GameModeClient::Requester on D-Bus
Expose the requeter process identifier as 'Requester' property
of the com.feralinteractive.GameMode.Game interface.
2019-07-16 19:00:35 +01:00
Christian Kellner
cfe0fb4f17 daemon: add requester to GameModeClient
Record the requester process id in the GameModeClient struct and
add a getter for it.
2019-07-16 19:00:33 +01:00
Christian Kellner
288b3a005e daemon: export GameModeClient::Timestamp on D-Bus
Expose the timestamp that a client was created as 'Timestamp'
property of the com.feralinteractive.GameMode.Game interface.
2019-07-16 18:59:44 +01:00
Christian Kellner
52367772c8 daemon: add creation timestamp to GameModeClient
Record the time a client was created, i.e. registered, in the
GameModeClient struct and add a getter for it.

(Alex Smith: Fixed up function documentation comments)
2019-07-16 18:59:01 +01:00
Christian Kellner
6f39ecbc9b meson: display util building status
We show the status for examples & daemon, also show it for 'util'.
2019-07-16 18:58:11 +01:00
Alex Smith
ffea085396 Follow up improvements to documentation for GAMEMODERUNEXEC 2019-07-16 08:17:35 +01:00
Stephan Lachnit
47db83e509 Add hybrid GPU support
Signed-off-by: Stephan Lachnit <stephanlachnit@protonmail.com>
2019-07-14 16:45:33 +02:00
Alex Smith
c7c464bea6 Merge pull request #158 from gicmo/objs_fix
daemon: properly report error in error case
2019-07-13 09:57:25 +01:00
Alex Smith
c6d1b45bfb Use the appropriate library path for whether the app is 32- or 64-bit in gamemoderun (#156) 2019-07-13 09:50:25 +01:00
Christian Kellner
9075829526 daemon: fix typo in Game.Executable dbus property
Fix spelling for the 'Executable' property in the interface
com.feralinteractive.GameMode.Game.
2019-07-12 17:01:01 +02:00
Christian Kellner
02ad53584d daemon: properly report error in error case
Currently, in method_list_games (`ListGames` on the bus), when
sd_bus_message_append fails, we break the loop but then we were
not reporting the error (contained in r) back to call.
2019-07-12 11:35:55 +02:00
Alex Smith
3881b8b2c8 Fix clang-format error 2019-07-07 11:42:31 +01:00
Alex Smith
60c68feea6 Use the GAME_PATH_PREFIX definition everywhere 2019-07-06 18:12:48 +01:00
Christian Kellner
f9827edfb6 daemon: emit client count change automatically
Every time a game is (un)-registered and we emit the corresponding
signal, also emit the properties change signal for ClientCount.
2019-07-04 16:04:35 +02:00
Christian Kellner
d2bab2962d daemon: export game objects on the bus
For each registered game, export an object on the bus under the
path "/com/feralinteractive/GameMode/Games/<pid>" with an dbus
interface of ""com.feralinteractive.GameMode.Game". The interface
currently provides to properties, ProcessId and Executable.
Additionally add the ListGames method and the GameRegistered,
GameUnregistered signals to the com.feralinteractive.GameMode
interface.
2019-07-04 16:04:35 +02:00
Christian Kellner
5949a988ea daemon: add game_mode_context_lookup_client method
Like game_mode_context_has_client, but will add a reference to
the client, if a match was found.
2019-07-02 17:47:05 +02:00
Christian Kellner
208f37b7d1 daemon: method to list all registered clients
Return an array of pid_t elements containing the process ids of
all registered clients. Memory ownership is transferred to the
client and must be freed.
2019-07-02 17:47:05 +02:00
Christian Kellner
455ea0c72e daemon: getters for properties of GameModeClient
Add getters for all two properties of GameModeCLient: the process
id and the executable path.
2019-07-02 17:46:52 +02:00
Christian Kellner
d99af40795 daemon: add game_mode_client_ref
Now that GameModeClient is reference counted, the counterpart to
game_mode_client_unref is also needed.
2019-07-02 17:46:31 +02:00
Christian Kellner
7e10cc3a0b daemon: make game_mode_client_unref public
So it can be used outside ouf gamemode-context.c.
2019-07-02 17:46:24 +02:00
Christian Kellner
1a863f32a1 daemon: add ref-counting to GameModeClient
This is so it can out-live its membership in the client list, e.g.
when it is passed outside of gamemode-context.c and the reaper
comes along and reaps a client in the background but we still are
using the struct outside.
2019-07-02 17:46:17 +02:00
Christian Kellner
2027e981e6 daemon: expose GameModeClient as opaque struct
First step to making GameModeClient useful outside of gamemode-
context.c.
2019-07-02 17:45:55 +02:00
Alex Smith
b3cec8d901 Merge pull request #152 from ysblokje/disable_utils
added option to disable building the utils
2019-06-03 08:13:20 +01:00
Alex Smith
ebbb9a3511 Fix libdbus-1-dev package name typo 2019-06-03 08:10:30 +01:00
Minze Zwerver
15ff22c745 added option to disable building the utils
When you need both 32 and 64 bit version of the library only
one version of the utils is required.
2019-06-02 12:35:33 +02:00
Alex Smith
e5286e1495 Add Three Kingdoms to list of games with integration 2019-06-01 13:50:24 +01:00
Alex Smith
b0c36c0eaa Remove unused LOG_ERROR, always log errors 2019-06-01 13:49:37 +01:00
Alex Smith
2a124ce8c7 Merge pull request #151 from mdiluz/patch-1
Turn off debug logging in client impl
2019-06-01 13:46:06 +01:00
Alex Smith
fc46ffc463 Merge pull request #148 from mdiluz/the-big-cleanup
Spring cleaning
2019-06-01 13:44:38 +01:00
Marc Di Luzio
e9ab20be60 Add comments to the two parts where clang-format is off 2019-06-01 11:18:08 +01:00
Marc Di Luzio
702407595a Rename duplicate CONFIG_VALUE_MAX
Leave a static assert to ensure they're in sync when relevant in gamemode-gpu
2019-06-01 11:12:10 +01:00
Marc Di Luzio
a5e00bc94e Turn off debug logging in client impl
Accidentally left in #147
2019-06-01 11:00:33 +01:00
Marc Di Luzio
c5c966ad54 Add back needed include 2019-06-01 10:57:27 +01:00
Marc Di Luzio
e537caf170 Ensure we're more specific about clang format options and include order 2019-06-01 10:57:27 +01:00
Marc Di Luzio
2e67906402 Remove unneeded includes 2019-06-01 10:57:27 +01:00
Marc Di Luzio
28243afde9 Clean up the main readme file and split some parts out
This groups development, daemon and lib parts together much better
2019-06-01 10:56:27 +01:00
Marc Di Luzio
98e656f9ec Clean up self explanatory comments 2019-06-01 10:54:22 +01:00
Marc Di Luzio
1b78d0dcf7 Restructure files and libraries
Rename a bunch of files to make the consistent
	Create two new subdirectories for common code, and utilities
2019-06-01 10:54:22 +01:00
Marc Di Luzio
41988b7f1c Move code only used for Wine detection into wine file 2019-06-01 10:54:22 +01:00
Marc Di Luzio
2bca1fb04e Remove commented includes 2019-06-01 10:54:22 +01:00
Marc Di Luzio
81e38d02e6 Move daemonise code to main file, simplifying files 2019-06-01 10:54:22 +01:00
Marc Di Luzio
9d484061ad Remove extra comments in config that didn't explain anything 2019-06-01 10:54:22 +01:00
Marc Di Luzio
717d6cc003 Correct the inotify failure message 2019-06-01 10:54:22 +01:00
Marc Di Luzio
1df1852c76 Move most Wine could out to Wine file 2019-06-01 10:54:22 +01:00
Marc Di Luzio
75dc083616 Remove artificial max client limit
This is just additional code that isn't required, and could mask or cause problems.
2019-06-01 10:54:22 +01:00
Alex Smith
a837b8630c We depend on DBus development packages now 2019-05-29 16:02:49 +01:00
Alex Smith
37108d7c6b Merge branch 'libdbus' 2019-05-29 15:53:27 +01:00
Alex Smith
7ebe633026 Merge pull request #150 from mdiluz/fix-inotify
inotify fix
2019-05-29 15:36:19 +01:00
Marc Di Luzio
71d4fab15e Fix inotify for https://github.com/FeralInteractive/gamemode/issues/149
1. Don't look for ATTRIB changes - these spawn extra unneeded events when other watches will catch actual edits
	2. Move name check code out of IN_ISDIR, I mistook IN_ISDIR to mean the event was from a dir watch, but this was incorrect
2019-05-28 16:37:34 +01:00
Christian Kellner
e87a8f19f3 lib: use libdbus for client's dbus messaging
Switch the dbus implementation for the client from systemd to
libdbus. The main reason is that, in flatpaks systemd is not easily
available. No phenomenological change for users of the library,
hopefully.
2019-05-27 13:44:44 +02:00
Alex Smith
3a2ebd1cdf Merge pull request #144 from mdiluz/add-config-hotreloading
Add config hot-reloading
2019-05-24 08:58:43 +01:00
Marc Di Luzio
a0b850474d Fix comments 2019-05-24 08:48:38 +01:00
Marc Di Luzio
a9e3f866a0 Extend the context rwlock around where we apply optimisations
This prevents a potential race condition for when the reaper thread reloads the config
2019-05-24 08:45:14 +01:00
Alex Smith
128d9b1364 Merge pull request #145 from mdiluz/fix-clang-static-analyser-issues
Add a clang static analyser check and fix issues
2019-05-24 08:19:43 +01:00
Alex Smith
c502e00b7c Merge pull request #146 from gicmo/flatpak_portal
Add support for flatpak portal
2019-05-24 08:15:45 +01:00
Marc Di Luzio
83c4d38858 Use PATH_MAX-1 as strncopy doesn't always set the final null byte 2019-05-21 09:06:27 +01:00
Christian Kellner
0c36f3a6b0 Talk to the portal when running inside a flatpak
When we detect that we are running inside a flatpak, talk to the
flatpak portal D-Bus service instead of the session daemon. This
is necessary because flatpak uses pid namespace isolation (see
man pid_namespaces(7)) and thus the pid needs to be translated
from the flatpak pid namespace to the host namespace. This
translation is happening inside the GameMode xdg-desktop-portal.
2019-05-21 00:34:45 +02:00
Christian Kellner
536d687c9a Use defines for dbus name, path, interface
That gives some meaning to the strings and makes it easier to see
what the arguments of sd_bus_call_method mean without checking the
function docs.
2019-05-20 14:39:09 +02:00
Marc Di Luzio
44ab695246 Fix memory leak ambiguity with executable names 2019-05-19 12:47:32 +01:00
Marc Di Luzio
9db7442a31 Fix potential memory leak in game_mode_initialise_gpu 2019-05-19 12:47:32 +01:00
Marc Di Luzio
23dd471f6b Add clang analyzer check using scan-build 2019-05-19 12:47:32 +01:00
Marc Di Luzio
89904602e9 Fix formatting for travis 2019-05-19 11:24:51 +01:00
Marc Di Luzio
c276f760c7 Add comment about the reaper thread dealing with config file changes 2019-05-19 11:15:47 +01:00
Marc Di Luzio
5d0a413035 Only perform the leave/enter loop when gamemode is already active/has clients 2019-05-19 11:15:47 +01:00
Marc Di Luzio
ec55bda3b2 Implement inotify check on the reaper thread for config changes 2019-05-19 11:15:47 +01:00
Marc Di Luzio
ceb1808c95 Initial implementation of a RefreshConfig dbus interface 2019-05-19 11:15:47 +01:00
Alex Smith
16e7d06083 Fix up error logging in game_mode_resolve_wine_preloader()
proc_path was never set to non-null but used for error logging, this
triggered -Wformat-overflow warnings on GCC 9. Use the pid instead
and remove that variable.
2019-05-19 11:12:17 +01:00
Alex Smith
b04e39df43 Merge pull request #143 from mdiluz/client-example-check
Small client header improvements
2019-05-19 11:06:48 +01:00
Alex Smith
09e475e092 Typo fixes 2019-05-19 11:05:41 +01:00
Alex Smith
139b644d6d Merge pull request #142 from mdiluz/fixes-and-longoptions
ioprio and niceness fixes, and long cmdline options
2019-05-19 11:01:03 +01:00
Marc Di Luzio
f41ecda047 Fix a missing e 2019-05-15 18:58:12 +01:00
Marc Di Luzio
7ede50af39 Make sure we actually build the examples in the travis tests
Otherwise failing changes to gamemode_client.h risk passing
2019-05-15 18:56:14 +01:00
Marc Di Luzio
ddecc89f10 Add a note about GAMEMODE_AUTO and blocking calls 2019-05-15 18:56:14 +01:00
Marc Di Luzio
ddc802573a Remove unneeded includes in gamemode_client.h 2019-05-15 18:56:14 +01:00
Marc Di Luzio
a27e741beb Reformat long_options now that travis matches 2019-05-15 18:43:42 +01:00
Marc Di Luzio
f401b49b1a Close the openned task dirs to prevent a leak 2019-05-15 18:20:36 +01:00
Marc Di Luzio
41d35fa12a Add a zeroed final option for getopt_long for correctness 2019-05-15 18:17:50 +01:00
Marc Di Luzio
0d018d91a8 Implement some error logging/checking and clean up some TODO comments 2019-05-13 19:44:22 +01:00
Marc Di Luzio
ef29b35258 Make sure we don't leave a hanging gamemode request from the child process
Otherwise other tests might have to wait for the reaper thread to clean it up
2019-05-13 19:44:22 +01:00
Marc Di Luzio
139b90e0b7 Refactor the process test function
Make it simply take a functor to the per tid get method
2019-05-13 19:44:22 +01:00
Marc Di Luzio
4091a0c532 Now that renice works - set it to a hard fail again
Also move some comments around
2019-05-13 19:44:22 +01:00
Marc Di Luzio
f00a89bc56 Actually apply setpriority to the thread not the client 2019-05-13 19:44:22 +01:00
Marc Di Luzio
2d19b61a38 Always log when getpriority fails - we will likely want to know 2019-05-13 19:44:22 +01:00
Marc Di Luzio
16c932f5d0 Adjust logs for generic process test function 2019-05-13 19:44:22 +01:00
Marc Di Luzio
f152ea9338 Initial implementation of applying renice to all process threads 2019-05-13 19:44:22 +01:00
Marc Di Luzio
3ac49385dc Add a test for renicing multithreaded processes 2019-05-13 19:44:22 +01:00
Marc Di Luzio
87cfd9c5a6 Remove error log about known issue now that it is fixed 2019-05-13 19:44:22 +01:00
Marc Di Luzio
38e48a2d8e Update logging to reflect thread, client relationship 2019-05-13 19:44:22 +01:00
Marc Di Luzio
bf2b057915 First pass at fix for ioprio not applying to process tree properly
See https://github.com/FeralInteractive/gamemode/issues/140
2019-05-13 19:44:22 +01:00
Marc Di Luzio
6639b17500 Make the multithreaded ioprio tests not cause a full failure 2019-05-13 19:44:22 +01:00
Marc Di Luzio
6869470f9b Fix feature status codes - failures here should be considered a failure 2019-05-13 19:44:22 +01:00
Marc Di Luzio
934b497603 Implement multithreaded test framework (and use for ioprio)
ioprio tests will now fail due to https://github.com/FeralInteractive/gamemode/issues/140
2019-05-13 19:44:22 +01:00
Marc Di Luzio
5ffe832faf Don't store the initial values
1. We don't use them anyway (though that could be a feature request)
	2. They weren't stored per-client, so would be incorrect anyway
2019-05-11 13:24:13 +01:00
Marc Di Luzio
7f196cdd1a Fix formatting for travis 2019-05-11 13:06:43 +01:00
Marc Di Luzio
598969a538 Implement restoring the old ioprio value when unregistered
Another part of the fix for https://github.com/FeralInteractive/gamemode/issues/141
2019-05-11 12:08:29 +01:00
Marc Di Luzio
2249a71355 Get ready for re-setting ioprio value on un-register
Implements tests for feature

	Fixes CLAMP macro
2019-05-11 12:08:29 +01:00
Marc Di Luzio
6d14149658 Reset renice value on unregister
Part of fixing up https://github.com/FeralInteractive/gamemode/issues/141
2019-05-11 12:08:29 +01:00
Marc Di Luzio
09d63ae4f5 Set up for resetting niceness value
Add tests to check this feature
	Apply config validation in config for simplicity

	Note: if anything messes with the niceness (ie. it starts non-zero, or it's not the expected value during setup, we'll bail out)
2019-05-11 12:08:29 +01:00
Marc Di Luzio
7e5216c4a0 Some comments and cleanup 2019-05-11 12:08:29 +01:00
Marc Di Luzio
a482b72d37 Fix comments in gamemode_client.h 2019-05-11 12:08:29 +01:00
Marc Di Luzio
717777e6c2 Implement PID support for --request and --status 2019-05-11 12:08:29 +01:00
Marc Di Luzio
baff9c0363 Allow for long options using getopt_long 2019-05-11 12:08:29 +01:00
Alex Smith
99c7d04e69 Add a link to the GNOME Shell extension 2019-05-07 09:30:33 +01:00
Alex Smith
4a49a1c2a5 Add a list of games which integrate GameMode support (#135) 2019-05-07 09:25:33 +01:00
Alex Smith
67c7aa04d6 Merge pull request #136 from gicmo/sandbox_root
Use readlink instead of realpath so find_exe works for flatpaks
2019-05-07 09:15:56 +01:00
Christian Kellner
70e601267b daemon: use readlink not realpath to find the exe
The realpath(3) will fail if the target does not exist (internally
realpath will stat all the components of the link target path).
This is a problem in the case of sandbox applications where
the exe points to the absolute path *inside* the sandbox, e.g. to
/app/bin/<name> in the case of flatpak. For these cases realpath(3)
will then fail. Therefore use readlink(3) instead.
2019-05-02 11:29:58 +02:00
Christian Kellner
0c778200ae helpers: add helpers to automatically close fds
Add a inline helper function and a helper macro to be able to
automatically close file descriptors. Does nothing if the argument
is NULL or the pointed to integer is < 0.
2019-05-02 11:29:03 +02:00
Alex Smith
001a69f565 Merge pull request #133 from gicmo/lib_bus_leak
lib: release acquired bus reference
2019-05-02 09:46:34 +01:00
Christian Kellner
393a5e8f41 lib: release acquired bus reference
On each gamemode_request call a new connection to d-bus is opened
but the reference was never release thus leaking the connection
and associated memory.
2019-04-30 11:46:50 +02:00
Alex Smith
6291c13cf4 Merge pull request #130 from rombert/patch-1
Add reference to openSUSE packages
2019-04-12 08:34:52 +01:00
Alex Smith
dbf9974fed Merge pull request #129 from gicmo/client_count_property
Introduce a ClientCount dbus Property
2019-04-12 08:33:47 +01:00
Robert Munteanu
0aced6f14e Add reference to openSUSE packages
OpenSUSE packages are available at https://software.opensuse.org/package/gamemode, so let's reflect this in the README.
2019-04-11 01:08:46 +02:00
Christian Kellner
05909d1cfa daemon: add a ClientCount dbus property
Introduce a new "ClientCount" dbus property that can be queried and
also watched to see if gamemode is currently active (or not).
2019-04-11 00:09:52 +02:00
Christian Kellner
6d921617f9 daemon: make context_num_clients a public method
So it can be used from outside daemon/gamemode.c, to e.g. implement
an ClientCount property.
2019-04-10 23:57:00 +02:00
Alex Smith
e86580c18c Update instructions to use 1.3.1 as the current stable release 2019-04-09 08:34:44 +01:00
Alex Smith
dbc5c453d5 Merge pull request #128 from mgerstner/gpuclockctl_fixes
Gpuclockctl fixes
2019-04-08 09:31:22 +01:00
Alex Smith
a91156ef93 Merge pull request #125 from tgurr/prefix
Use path_bindir for installing gamemoderun
2019-04-05 14:05:22 +01:00
Matthias Gerstner
78c2ced7d7 external-helper: improve run_external_process() robustness
run_external_process() contained pipe file descriptors leaks (e.g. one
pipe end was never closed). Also the stdout might have been captured
incomplete, since only a single read() was performed on the pipe.
Furthermore should a child process try to write a larger amount of data
onto the pipe then it will become stuck, because the parent process
isn't consuming the data. Thus the timeout would trigger in these cases
although the child process does nothing wrong.

This commit changes the implementation to follow a select() based
approach that continually reads from the pipe, but discards data that
doesn't fit in the provided buffer.
2019-04-04 13:19:22 +02:00
Matthias Gerstner
b411cfff6b gpuclockctl: refactor buffer size specification and avoid redundancies 2019-04-04 11:44:34 +02:00
Matthias Gerstner
d7394cbeb2 daemon: fix file descriptor leaks 2019-04-03 16:27:12 +02:00
Matthias Gerstner
618997f0b3 gpuclockctl: fix return value of get_gpu_state_amd() 2019-04-03 16:18:36 +02:00
Timo Gurr
85d75c303c Use path_bindir for installing gamemoderun
Honor prefix for installing gamemoderun to properly work in cross
environment so it can be installed in e.g. /usr/x86_64-pc-linux-gnu/bin
instead of /usr/bin like it's already done for gamemoded.
2019-03-29 18:18:23 +01:00
Alex Smith
959c48978b Update version to 1.4-dev post-release 2019-03-29 15:39:08 +00:00
Alex Smith
e5ccb4b68d Update version to 1.3.1 2019-03-29 15:30:11 +00:00
Alex Smith
1a51a2ec00 Update changelog for 1.3.1 2019-03-29 15:30:00 +00:00
Alex Smith
9465c5f6bd Generate sha256sums in mkrelease.sh 2019-03-29 15:25:25 +00:00
Alex Smith
efb8fbc3af Merge pull request #123 from gicmo/strtrunc_falsepositive
Rewrite strncpy to silence compiler warning
2019-03-29 15:21:34 +00:00
Christian Kellner
41b85c245d Rewrite strncpy to silence compiler warning
Instead of initializing the char array to 0 and writing N-1 bytes,
write N-1 bytes and set the last byte manually to 0. This seems
to make gcc happy.
2019-03-28 17:14:00 +01:00
Christian Kellner
34d86d30b1 Use additional compiler warnings, if supported
Check a list of well-known compiler warnings and use the one that
are supported by the current compiler.
2019-03-28 11:40:40 +00:00
Christian Kellner
4a577b8c7c Rename local variable to un-shadow the global bus
The local variable 'bus' was shadowing the global 'bus' variable;
to not confuse the two, rename the local one. Also unref the local
bus, i.e. fix a small memory leak.
2019-03-28 11:40:40 +00:00
Christian Kellner
c34186be07 Mark game_mode_context_loop as 'noreturn'
The function contains the main-loop of the daemon and will indeed
never return.
2019-03-28 11:40:40 +00:00
Christian Kellner
d12ab6830f No old-style function definitions.
Specify void as argument type for functions that don't take args.
2019-03-28 11:40:40 +00:00
Christian Kellner
45ba5bc4c4 Use matching signedness in format strings
Either cast the variable or change the format string to match the
signedness.
2019-03-28 11:40:40 +00:00
Christian Kellner
0eb59fc848 Use minimal number of characters for strncmp
MAX_GOVERNOR_LENGTH is defined as PATH_MAX + 1, so when comparing
two variable, each of once size, use the smaller one, i.e. PATH_MAX.
Assert statically that MAX_GOVERNOR_LENGTH is larger then PATH_MAX
so copying a string to a MAX_GOVERNOR_LENGTH sized variable from a
PATH_MAX size variable will not truncate the string.
2019-03-28 11:40:40 +00:00
Christian Kellner
bbde1d0357 Ensure strncpy'ed strings are all null terminated
If there is no null byte among the first n bytes of the source the
resulting string will not be properly null terminated.
Ensure that all strings that are copied via strncpy are properly
terminated copy "sizeof (dest) - 1" bytes and manually terminate
the string in the cases the array was not initialized.

Example compiler warning:
  ../daemon/gamemode-tests.c: In function ‘run_cpu_governor_tests’:
  ../daemon/gamemode-tests.c:326:4: warning: ‘strncpy’ specified bound
      256 equals destination size [-Wstringop-truncation]
    strncpy(defaultgov, currentgov, CONFIG_VALUE_MAX);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2019-03-28 11:40:40 +00:00
Alex Smith
db4dd87e22 Add Debian to the list of distros which have packages available 2019-03-25 16:49:25 +00:00
Alex Smith
c0032efc4f Make gamemoderun.in executable (#115)
The install_mode option was only added in Meson 0.47. In versions prior
to that, it will base the mode on the permissions of the source file,
so set this executable to be compatible with older versions.

See #115.
2019-03-25 16:40:54 +00:00
Alex Smith
5516b547ca Merge pull request #121 from lilianmoraru/master
Build with optimizations
2019-03-25 16:16:25 +00:00
Lilian A. Moraru
c0ffecae43 Build with optimizations 2019-03-19 10:38:49 +02:00
Alex Smith
95c365076f Update to version 1.4-dev post-release 2019-03-15 16:11:57 +00:00
90 changed files with 6937 additions and 2662 deletions

View File

@ -2,8 +2,7 @@
AccessModifierOffset: 0
AlignAfterOpenBracket: true
AlignConsecutiveAssignments: false
#uncomment for clang 3.9
#AlignConsecutiveDeclarations: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: false
AlignOperands: true
AlignTrailingComments: true
@ -14,21 +13,17 @@ AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
# AlwaysBreakAfterDefinitionReturnType: None
#uncomment for clang 3.9
#AlwaysBreakAfterReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: false
BinPackArguments: false
BinPackParameters: true
# BraceWrapping: (not set since BreakBeforeBraces is not Custom)
BreakBeforeBinaryOperators: None
# BreakAfterJavaFieldAnnotations: (not java)
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Linux
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
#uncomment for clang 3.9
#BreakStringLiterals: false
# Too new for travis clang-format version
# BreakStringLiterals: false
ColumnLimit: 100
CommentPragmas: '\*\<'
ConstructorInitializerAllOnOneLineOrOnePerLine: false
@ -39,35 +34,35 @@ DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
ForEachMacros: [ ]
#Uncomment for clang 3.9
#IncludeCategories:
# - Regex: '^"'
# Priority: 1
SortIncludes: true
# IncludeBlocksStyle changed to IncludeBlocks, between xenial and disco, so we can't use it for consistency
# IncludeBlocks: Regroup
IncludeCategories:
- Regex: '^<linux\/'
Priority: 0
- Regex: '^<'
Priority: 1
- Regex: '^"gamemode.h"'
Priority: 2
- Regex: '^"'
Priority: 3
# IncludeIsMainRegex: (project doesn't use a main includes that can add other includes via regex)
IndentCaseLabels: false
IndentWidth: 4
IndentWrappedFunctionNames: false
# JavaScriptQuotes: (not javascript)
KeepEmptyLinesAtTheStartOfBlocks: false
Language: Cpp
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
# ObjCBlockIndentWidth: (not objc)
# ObjCSpaceAfterProperty: (not objc)
# ObjCSpaceBeforeProtocolList: (not objc)
PenaltyBreakBeforeFirstCallParameter: 400
PenaltyBreakComment: 0
# PenaltyBreakFirstLessLess: (not cpp)
PenaltyBreakString: 500
PenaltyExcessCharacter: 10000
PenaltyReturnTypeOnItsOwnLine: 600
PointerAlignment: Right
#uncomment for clang 3.9
#ReflowComments: true
#uncomment for clang 3.9
#SortIncludes: true
ReflowComments: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
@ -75,7 +70,6 @@ SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
# SpacesInContainerLiterals: (not objc or javascript)
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11

41
.github/workflows/build-and-test.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Build and test
on: [push, pull_request]
permissions:
contents: read
actions: write
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential meson appstream clang clang-format clang-tools libdbus-1-dev libinih-dev libsystemd-dev systemd-dev
- name: Check format
env:
CI: "true"
run: |
./scripts/format-check.sh
- name: Build and install
env:
CI: "true"
run: |
./bootstrap.sh -Dwith-examples=true
- name: Tests
run: |
meson test -C builddir
- name: Simulate game
run: |
dbus-run-session -- gamemode-simulate-game
- name: Static analyser check
run: |
./scripts/static-analyser-check.sh
- name: Upload logs
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: logs
path: |
builddir/meson-logs/

5
.gitignore vendored
View File

@ -1,2 +1,3 @@
build/
*.swp
/builddir
/subprojects/packagecache/
/subprojects/inih-r*

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "subprojects/inih"]
path = subprojects/inih
url = https://github.com/FeralInteractive/inih.git

View File

@ -1,23 +0,0 @@
dist: xenial
language: c
compiler: gcc
sudo: false
addons:
apt:
packages:
- clang-format
- python3-pip
- python3-setuptools
- libsystemd-dev
- ninja-build
before_script:
- pip3 install wheel
- pip3 install meson
- meson --version
script:
- ./scripts/format-check.sh
- ./bootstrap.sh
- gamemoded -v

View File

@ -1,3 +1,168 @@
## 1.8.2
### Changes
* Fix idle inhibitor closing bus connection too early (#466)
* Fix hybrid CPU core pinning (#455)
* Fix unreadable process maps in gamemodelist (#463)
* Fixed crash if dbus is not accesible (#458)
* Various bugfixes and improvements to documentation
### Contributors
* @notpeelz
* @patatahooligan
* Reilly Brogan @ReillyBrogan
* Alexandru Ionut Tripon @Trial97
* Kostadin @kostadinsh
* Daniel Martinez @Calandracas606
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2023-12-13&to=2024-08-19&type=c)
## 1.8.1
### Changes
* Fix polkit parse error (#449)
### Contributors
* @Vam-Jam
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2022-07-22&to=2023-12-12&type=c)
## 1.8
### Changes
* Add CPU core pinning and parking capability (#416)
* Allow disabling the Linux kernel split lock mitigation (#446)
* Fix building when pidfd_open is available (Fixes build with glibc 2.36) (#379)
* Unify privileged group configuration between pam, systemd, & polkit (#375)
* Various other bugfixes and improved default configuration
### Contributors
* Henrik Holst @HenrikHolst
* Kira Bruneau @kira-bruneau
* James Le Cuirot @chewi
* Hugo Locurcio @Calinou
* Zoltán Nyikos @nyz93
* @ashuntu
* @szymon-gniado
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2022-07-22&to=2023-12-06&type=c)
## 1.7
### Changes
* Added new utility: `gamemodelist` (#346)
* Run executables from `PATH` instead of `/usr/bin` (#323)
* Add a trivial `gamemode.conf` file, which creates the gamemode group (#339)
* Various minor bugfixes and updates to documentation
### Contributors
* Sam Gleske @samrocketman
* Kira Bruneau @kira-bruneau
* Stephan Lachnit @stephanlachnit
* Emil Velikov @evelikov-work
[View the full list of contributors](https://github.com/FeralInteractive/gamemode/graphs/contributors?from=2021-02-19&to=2022-07-21&type=c)
## 1.6.1
### Changes
* Use inih r53
* Packaging changes for Arch
* Minor metainfo improvements
### Contributors
* Stephan Lachnit @stephanlachnit
* Alberto Oporto Ames @otreblan
## 1.6
### Changes
* Created new manpages for `gamemoderun` and the example, now called `gamemode-simulate-game`
* Add ability to change lib directory of `gamemoderun`
* Add option to use `elogind`
* Copy default config file to the correct location
* Allow `LD_PRELOAD` to be overridden in `$GAMEMODERUNEXEC`
* Various minor bugfixes
* Improvements to dependency management
### Contributors
* Stephan Lachnit @stephanlachnit
* Rafał Mikrut @qarmin
* Niels Thykier @nthykier
* Stéphane Gleizes @sgleizes
## 1.5.1
### Changes
Minor changes for Debian and Ubuntu packaging:
* Use the preferred logging system rather than defaulting to syslog.
* Prefer the system installation of inih.
### Contributors
* Sebastien Bacher @seb128
* Stephan Lachnit @stephanlachnit
## 1.5
### Changes
* Introduce a new pidfd based set of D-Bus APIs (#173)
* Dynamically change governor on integrated GPUs for improved performance (#179)
* Various other fixes and improvements.
### Contributors
* Alex Smith @aejsmith
* Christian Kellner @gicmo
* Faith Ekstrand @gfxstrand
## 1.4
### Changes
* Add new D-Bus methods/properties for use by external tools such as the [GameMode GNOME Shell extension](https://github.com/gicmo/gamemode-extension/) (#129, #155, #161).
* Fix I/O priority and niceness optimisations to apply to the whole process rather than just the thread that requests GameMode (#142).
* `gamemoded` will now automatically reload the configuration file when it is changed and update optimisations on current clients (#144).
* Add support for using the client library inside Flatpak by communicating with the daemon via a portal (#146).
* Client library now uses libdbus rather than sd-bus (#147).
* Fix `gamemoderun` to use the correct library path depending on whether the app is 32-bit or 64-bit.
* Support the `GAMEMODERUNEXEC` environment variable to specify an extra wrapper command for games launched with `gamemoderun` (e.g. a hybrid GPU wrapper such as `optirun`) (#159).
* Various other fixes and improvements.
### Contributors
* Christian Kellner @gicmo
* Marc Di Luzio @mdiluz
* Matthias Gerstner @mgerstner
* Minze Zwerver @ysblokje
* Stephan Lachnit @stephanlachnit
* Timo Gurr @tgurr
## 1.3.1
### Changes
* Change permission of `gamemoderun` in source tree so that it is correctly installed with execute permissions on older Meson versions (such as that included with Ubuntu 18.04) (#115).
* Enable more compiler warnings and fix issues highlighted by these.
### Contributors
* Christian Kellner @gicmo
## 1.3
### Changes

View File

@ -1,4 +1,4 @@
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without

224
README.md
View File

@ -1,69 +1,28 @@
# GameMode
**GameMode** is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS.
**GameMode** is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS and/or a game process.
GameMode was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is now host to a range of optimisation features and configurations.
Currently GameMode includes support for optimisations including:
* CPU governor
* I/O priority
* Process niceness
* Kernel scheduler (`SCHED_ISO`)
* Screensaver inhibiting
* GPU performance mode (NVIDIA and AMD), GPU overclocking (NVIDIA)
* CPU core pinning or parking
* Custom scripts
GameMode packages are available for Ubuntu, Debian, Solus, Arch, Gentoo, Fedora, OpenSUSE, Mageia and possibly more.
Issues with GameMode should be reported here in the issues section, and not reported to Feral directly.
---
## Building and installing [![Build Status](https://travis-ci.org/FeralInteractive/gamemode.svg?branch=master)](https://travis-ci.org/FeralInteractive/gamemode)
*It is preferable to install GameMode with your package manager of choice, if available*. There are Ubuntu (Cosmic), Solus, AUR, Gentoo and Fedora packages available at the time of writing.
### Install Dependencies
GameMode depends on `meson` for building and `systemd` for internal communication. This repo contains a `bootstrap.sh` script to allow for quick install to the user bus, but check `meson_options.txt` for custom settings.
#### Ubuntu/Debian (you may also need `dbus-user-session`)
```bash
apt install meson libsystemd-dev pkg-config ninja-build git
```
#### Arch
```bash
pacman -S meson systemd git
```
#### Fedora
```bash
dnf install meson systemd-devel pkg-config git
```
#### Gentoo
Gentoo has an ebuild which builds a stable release from sources. It will also pull in all the dependencies so you can work on the source code.
```bash
emerge --ask games-util/gamemode
```
You can also install using the latest sources from git:
```bash
ACCEPT_KEYWORDS="**" emerge --ask ~games-util/gamemode-9999
```
### Build and Install GameMode
Then clone, build and install a release version of GameMode at 1.3:
```bash
git clone https://github.com/FeralInteractive/gamemode.git
cd gamemode
git checkout 1.3 # omit to build the master branch
./bootstrap.sh
```
To uninstall:
```bash
systemctl --user stop gamemoded.service
cd build/
ninja uninstall
```
---
## Requesting GameMode
After installing `libgamemodeauto.so.0` simply preload it into the game:
For games/launchers which integrate GameMode support, simply running the game will automatically activate GameMode.
For others, you must manually request GameMode when running the game. This can be done by launching the game through `gamemoderun`:
```bash
gamemoderun ./game
```
@ -76,103 +35,120 @@ Note: for older versions of GameMode (before 1.3) use this string in place of `g
```
LD_PRELOAD="$LD_PRELOAD:/usr/\$LIB/libgamemodeauto.so.0"
```
Please note the backslash here in `\$LIB` is required.
**Please note the backslash here in `\$LIB` is required.**
---
## Configuration
The daemon can currently be configured using a `gamemode.ini` file. [gamemode.ini](https://github.com/FeralInteractive/gamemode/blob/master/example/gamemode.ini) is an example of what this file would look like, with explanations for all the variables.
The daemon is configured with a `gamemode.ini` file. [example/gamemode.ini](https://github.com/FeralInteractive/gamemode/blob/master/example/gamemode.ini) is an example of what this file would look like, with explanations for all the variables.
Config files are loaded and merged from the following directories, in order:
1. `/usr/share/gamemode/`
2. `/etc/`
3. `$XDG_CONFIG_HOME` or `$HOME/.config/`
4. `$PWD`
Configuration files are loaded and merged from the following directories, from highest to lowest priority:
The file parsing uses [inih](https://github.com/benhoyt/inih).
1. `$PWD` ("unsafe" - **`[gpu]` settings take no effect in this file**)
2. `$XDG_CONFIG_HOME` or `$HOME/.config/` ("unsafe" - **`[gpu]` settings take no effect in this file**)
3. `/etc/`
4. `/usr/share/gamemode/`
---
## Features
## Note for Hybrid GPU users
### Scheduling
GameMode can leverage support for soft real time mode if the running kernel supports `SCHED_ISO` (not currently supported in upstream kernels), controlled by the `softrealtime` option. This adjusts the scheduling of the game to real time without sacrificing system stability by starving other processes.
It's not possible to integrate commands like optirun automatically inside GameMode, since the GameMode request is made once the game has already started. However it is possible to use a hybrid GPU wrapper like optirun by starting the game with `gamemoderun`.
GameMode can adjust the nice priority of games to give them a slight IO and CPU priority over other background processes, controlled by the `renice` option. This only works if your user is permitted to adjust priorities within the limits configured by PAM. GameMode can be configured to take care of it by passing `with-pam-group=group` to the build options where `group` is a group your user needs to be part of.
For more information, see `/etc/security/limits.conf`.
You can do this by setting the environment variable `GAMEMODERUNEXEC` to your wrapper's launch command, so for example `GAMEMODERUNEXEC=optirun`, `GAMEMODERUNEXEC="env DRI_PRIME=1"`, or `GAMEMODERUNEXEC="env __NV_PRIME_RENDER_OFFLOAD=1 __GLX_VENDOR_LIBRARY_NAME=nvidia __VK_LAYER_NV_optimus=NVIDIA_only"`. This environment variable can be set globally (e.g. in /etc/environment), so that the same prefix command does not have to be duplicated everywhere you want to use `gamemoderun`.
Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage across all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). This value also protects against compromising system stability, do not set it to 100% as this would turn the game into a full real time process, thus potentially starving all other OS components from CPU resources.
### IO priority
GameMode can adjust the I/O priority of games to benefit from reduced lag and latency when a game has to load assets on demand. This is done by default.
### For those with overclocked CPUs
If you have an AMD CPU and have disabled Cool'n'Quiet, or you have an Intel CPU and have disabled SpeedStep, then GameMode's governor settings will not work, as your CPU is not running with a governor. You are already getting maximum performance.
If you are unsure, `bootstrap.sh` will warn you if your system lacks CPU governor control.
Scripts and other features will still work.
### GPU optimisations
GameMode is able to automatically apply GPU performance mode changes on AMD and NVIDIA, and overclocking on NVIDIA, when activated. AMD support currently requires the `amdgpu` kernel module, and NVIDIA requires the `coolbits` extension to be enabled in the NVIDIA settings.
It is very much encouraged for users to find out their own overclocking limits manually before venturing into configuring them in GameMode, and activating this feature in GameMode assumes you take responsibility for the effects of said overclocks.
More information can be found in the `example/gamemode.ini` file.
Note that both NVIDIA (GPUBoost) and AMD (Overdrive) devices and drivers already attempt to internally overclock if possible, but it is still common for enthusiasts to want to manually push the upper threshold.
GameMode will not be injected to the wrapper.
---
## Developers
## Development [![Build and test](https://github.com/FeralInteractive/gamemode/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/FeralInteractive/gamemode/actions/workflows/build-and-test.yml)
The design of GameMode has a clear-cut abstraction between the host daemon and library (`gamemoded` and `libgamemode`), and the client loaders (`libgamemodeauto` and `gamemode_client.h`) that allows for safe use without worrying about whether the daemon is installed or running. This design also means that while the host library currently relies on `systemd` for exchanging messages with the daemon, it's entirely possible to implement other internals that still work with the same clients.
### Components
**gamemoded** runs in the background, activates game mode on request, refcounts and also checks caller PID lifetime. Run `man gamemoded` for command line options.
See repository subdirectories for information on each component.
**libgamemode** is an internal library used to dispatch requests to the daemon. Note: `libgamemode` should never be linked with directly.
### Install Dependencies
GameMode depends on `meson` for building and `systemd` for internal communication. This repo contains a `bootstrap.sh` script to allow for quick install to the user bus, but check `meson_options.txt` for custom settings. These instructions all assume that you
already have a C development environment (gcc or clang, libc-devel, etc) installed.
**libgamemodeauto** is a simple dynamic library that automatically requests game mode when loaded. Useful to `LD_PRELOAD` into any game as needed.
**gamemode\_client.h** is as single header lib that lets a game request game mode and handle errors.
### Integration
Developers can integrate the request directly into an app. Note that none of these client methods force your users to have the daemon installed or running - they will safely no-op if the host is missing.
```C
// Manually with error checking
#include "gamemode_client.h"
if( gamemode_request_start() < 0 ) {
fprintf( stderr, "gamemode request failed: %s\n", gamemode_error_string() );
}
/* run game... */
gamemode_request_end(); // Not required, gamemoded can clean up after game exits
#### Ubuntu/Debian
Note: Debian 13 and Ubuntu 25.04 (and later) need to install `systemd-dev` in addition to the dependencies below.
```bash
apt update && apt install meson libsystemd-dev pkg-config ninja-build git dbus-user-session libdbus-1-dev libinih-dev build-essential
```
```C
// Automatically on program start and finish
#define GAMEMODE_AUTO
#include "gamemode_client.h"
On Debian 12 and Ubuntu 22 (and earlier), you'll need to install `python3` and `python3-venv` packages to install the latest meson version from `pip`.
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install meson
```
Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your linker arguments, or add it to an LD\_PRELOAD in a launch script.
Later you can deactivate the virtual environment and remove it.
### Supervisor support
Developers can also create apps that manage GameMode on the system, for other processes:
```C
#include "gamemode_client.h"
gamemode_request_start_for(gamePID);
gamemode_request_end_for(gamePID);
```bash
deactivate
rm -rf .venv
```
This functionality can also be controlled in the config file in the `supervisor` section.
#### Arch
```bash
pacman -S meson systemd git dbus libinih gcc pkgconf
```
---
## Contributions
#### RHEL 10 and variants
Note: Older versions of RHEL (and variants) cannot build gamemode due to not exposing libdbus-1 to pkg-config.
(also - don't try and play games on RHEL, come on)
You must have [EPEL](https://docs.fedoraproject.org/en-US/epel/getting-started/) enabled to install all dependencies.
```bash
dnf install meson systemd-devel pkg-config git dbus-devel inih-devel
```
#### Fedora
```bash
dnf install meson systemd-devel pkg-config git dbus-devel inih-devel
```
#### OpenSUSE Leap/Tumbleweed
```bash
zypper install meson systemd-devel git dbus-1-devel libgcc_s1 libstdc++-devel libinih-devel
```
#### Gentoo
Gentoo has an ebuild which builds a stable release from sources. It will also pull in all the dependencies so you can work on the source code.
```bash
emerge --ask games-util/gamemode
```
You can also install using the latest sources from git:
```bash
ACCEPT_KEYWORDS="**" emerge --ask ~games-util/gamemode-9999
```
#### Nix
Similar to Gentoo, nixOS already has a package for gamemode, so we can use that to setup an environment:
```bash
nix-shell -p pkgs.gamemode.buildInputs pkgs.gamemode.nativeBuildInputs
```
### Build and Install GameMode
Then clone, build and install a release version of GameMode at 1.8.2:
```bash
git clone https://github.com/FeralInteractive/gamemode.git
cd gamemode
git checkout 1.8.2 # omit to build the master branch
./bootstrap.sh
```
To test GameMode installed and will run correctly:
```bash
gamemoded -t
```
To uninstall:
```bash
systemctl --user stop gamemoded.service
ninja uninstall -C builddir
```
### Pull Requests
Pull requests must match with the coding style found in the `.clang-format` file, please run this before committing:
@ -180,10 +156,6 @@ Pull requests must match with the coding style found in the `.clang-format` file
clang-format -i $(find . -name '*.[ch]' -not -path "*subprojects/*")
```
### Planned Features
* Additional mode-switch plugins
* Improved client state tracking (PID is unreliable)
### Maintained by
Feral Interactive
@ -192,6 +164,8 @@ See the [contributors](https://github.com/FeralInteractive/gamemode/graphs/contr
---
## License
Copyright © 2017-2019 Feral Interactive
Copyright © 2017-2025 Feral Interactive and the GameMode contributors
GameMode is available under the terms of the BSD 3-Clause License (Revised)
The "inih" library is distributed under the New BSD license

View File

@ -14,9 +14,9 @@ if [ ! -f "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor" ]; then
echo "This probably means that you have disabled processor scheduling features in your BIOS. See README.md (or GitHub issue #44) for more information."
echo "This means GameMode's CPU governor control feature will not work (other features will still work)."
if [ "$TRAVIS" != "true" ]; then
if [ "$CI" != "true" ]; then
# Allow to continue the install, as gamemode has other useful features
read -p "Would you like to continue anyway [Y/N]? " -r
read -p "Would you like to continue anyway [y/N]? " -r
[[ $REPLY =~ ^[Yy]$ ]]
fi
fi
@ -26,19 +26,25 @@ fi
# Echo the rest so it's obvious
set -x
meson --prefix=$prefix build -Dwith-systemd-user-unit-dir=/etc/systemd/user
cd build
ninja
meson setup builddir --prefix=$prefix --buildtype debugoptimized -Dwith-systemd-user-unit-dir=/etc/systemd/user "$@"
ninja -C builddir
# Verify user wants to install
set +x
if [ "$TRAVIS" != "true" ]; then
read -p "Install to $prefix? [Yy] " -r
if [ "$CI" != "true" ]; then
read -p "Install to $prefix? [y/N] " -r
[[ $REPLY =~ ^[Yy]$ ]]
fi
set -x
sudo ninja install
sudo ninja install -C builddir
# Reload systemd configuration so that it picks up the new service.
systemctl --user daemon-reload
if [ "$CI" != "true" ]; then
# Restart polkit so we don't get pop-ups whenever we pkexec
if systemctl list-unit-files | grep -q polkit.service; then
sudo systemctl try-restart polkit
fi
# Reload systemd configuration so that it picks up the new service.
systemctl --user daemon-reload
fi

71
common/common-cpu.c Normal file
View File

@ -0,0 +1,71 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
#include "common-cpu.h"
#include "common-logging.h"
char *parse_cpulist(char *cpulist, long *from, long *to)
{
if (!cpulist || *cpulist == '\0')
return NULL;
char *endp;
*from = strtol(cpulist, &endp, 10);
if (endp == cpulist)
return NULL;
if (*endp == '\0' || *endp == ',') {
*to = *from;
if (*endp == '\0')
return endp;
return endp + 1;
}
if (*endp != '-')
return NULL;
cpulist = endp + 1;
*to = strtol(cpulist, &endp, 10);
if (endp == cpulist)
return NULL;
if (*to < *from)
return NULL;
if (*endp == '\0')
return endp;
return endp + 1;
}

51
common/common-cpu.h Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
#pragma once
#define _GNU_SOURCE
#include <sched.h>
#include <stdlib.h>
#define IS_CPU_PARK 0
#define IS_CPU_PIN 1
/* Storage for CPU info*/
struct GameModeCPUInfo {
size_t num_cpu;
int park_or_pin;
cpu_set_t *online;
cpu_set_t *to_keep;
};
/* parses a list of cpu cores in the format "a,b-c,d-e,f" */
char *parse_cpulist(char *cpulist, long *from, long *to);

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,16 +31,76 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "external-helper.h"
#include "logging.h"
#include "common-external.h"
#include "common-logging.h"
#include <linux/limits.h>
#include <stdio.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
static const int default_timeout = 5;
static const int DEFAULT_TIMEOUT = 5;
static int read_child_stdout(int pipe_fd, char buffer[EXTERNAL_BUFFER_MAX], int tsec)
{
fd_set fds;
struct timeval timeout;
int num_readable = 0;
ssize_t buffer_bytes_read = 0;
ssize_t just_read = 0;
bool buffer_full = false;
char discard_buffer[EXTERNAL_BUFFER_MAX];
/* Set up the timout */
timeout.tv_sec = tsec;
timeout.tv_usec = 0;
FD_ZERO(&fds);
/* Wait for the child to finish up with a timout */
while (true) {
FD_SET(pipe_fd, &fds);
num_readable = select(pipe_fd + 1, &fds, NULL, NULL, &timeout);
if (num_readable < 0) {
if (errno == EINTR) {
continue;
} else {
LOG_ERROR("sigtimedwait failed: %s\n", strerror(errno));
return -1;
}
} else if (num_readable == 0) {
return -2;
}
if (!buffer_full) {
just_read = read(pipe_fd,
buffer + buffer_bytes_read,
EXTERNAL_BUFFER_MAX - (size_t)buffer_bytes_read - 1);
} else {
just_read = read(pipe_fd, discard_buffer, EXTERNAL_BUFFER_MAX - 1);
}
if (just_read < 0) {
return -1;
} else if (just_read == 0) {
// EOF encountered
break;
}
if (!buffer_full) {
buffer_bytes_read += just_read;
if (buffer_bytes_read == EXTERNAL_BUFFER_MAX - 1) {
// our buffer is exhausted, discard the rest
// of the output
buffer_full = true;
}
}
}
buffer[buffer_bytes_read] = 0;
return 0;
}
/**
* Call an external process
@ -50,6 +110,7 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
pid_t p;
int status = 0;
int pipes[2];
int ret = 0;
char internal[EXTERNAL_BUFFER_MAX] = { 0 };
if (pipe(pipes) == -1) {
@ -59,20 +120,12 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
/* Set the default timeout */
if (tsec == -1) {
tsec = default_timeout;
}
/* set up our signaling for the child and the timout */
sigset_t mask;
sigset_t omask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
if (sigprocmask(SIG_BLOCK, &mask, &omask) < 0) {
LOG_ERROR("sigprocmask failed: %s\n", strerror(errno));
return -1;
tsec = DEFAULT_TIMEOUT;
}
if ((p = fork()) < 0) {
close(pipes[0]);
close(pipes[1]);
LOG_ERROR("Failed to fork(): %s\n", strerror(errno));
return false;
} else if (p == 0) {
@ -87,42 +140,34 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
* bindings that these objects are completely constant.
* http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
*/
if (execv(exec_args[0], (char *const *)exec_args) != 0) {
if (execvp(exec_args[0], (char *const *)exec_args) != 0) {
LOG_ERROR("Failed to execute external process: %s %s\n", exec_args[0], strerror(errno));
exit(EXIT_FAILURE);
}
_exit(EXIT_SUCCESS);
}
/* Set up the timout */
struct timespec timeout;
timeout.tv_sec = tsec;
timeout.tv_nsec = 0;
/* Wait for the child to finish up with a timout */
while (true) {
if (sigtimedwait(&mask, NULL, &timeout) < 0) {
if (errno == EINTR) {
continue;
} else if (errno == EAGAIN) {
LOG_ERROR("Child process timed out for %s, killing and returning\n", exec_args[0]);
kill(p, SIGKILL);
} else {
LOG_ERROR("sigtimedwait failed: %s\n", strerror(errno));
return -1;
}
}
break;
// should never be reached
abort();
}
// close the write end of the pipe so we get signaled EOF once the
// child exits
close(pipes[1]);
ssize_t output_size = read(pipes[0], internal, EXTERNAL_BUFFER_MAX - 1);
if (output_size < 0) {
LOG_ERROR("Failed to read from process %s: %s\n", exec_args[0], strerror(errno));
return -1;
}
ret = read_child_stdout(pipes[0], internal, tsec);
close(pipes[0]);
internal[output_size] = 0;
if (ret != 0) {
if (ret == -2) {
LOG_ERROR("Child process timed out for %s, killing and returning\n", exec_args[0]);
kill(p, SIGKILL);
} else {
LOG_ERROR("Failed to read from process %s: %s\n", exec_args[0], strerror(errno));
}
if (buffer) {
// make sure the buffer is a terminated empty string on error
buffer[0] = 0;
}
} else if (buffer) {
memcpy(buffer, internal, EXTERNAL_BUFFER_MAX);
}
if (waitpid(p, &status, 0) < 0) {
LOG_ERROR("Failed to waitpid(%d): %s\n", (int)p, strerror(errno));
@ -133,13 +178,10 @@ int run_external_process(const char *const *exec_args, char buffer[EXTERNAL_BUFF
if (!WIFEXITED(status)) {
LOG_ERROR("Child process '%s' exited abnormally\n", exec_args[0]);
} else if (WEXITSTATUS(status) != 0) {
LOG_ERROR("External process failed with exit code %u\n", WEXITSTATUS(status));
LOG_ERROR("Output was: %s\n", buffer ? buffer : internal);
LOG_ERROR("External process failed with exit code %d\n", WEXITSTATUS(status));
LOG_ERROR("Output was: %s\n", internal);
return -1;
}
if (buffer)
memcpy(buffer, internal, EXTERNAL_BUFFER_MAX);
return 0;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,12 +31,11 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "governors-query.h"
#include "logging.h"
#include "common-governors.h"
#include "common-logging.h"
#include <assert.h>
#include <glob.h>
#include <stdio.h>
#include <string.h>
/**
* Discover all governers on the system.
@ -80,12 +79,13 @@ int fetch_governors(char governors[MAX_GOVERNORS][MAX_GOVERNOR_LENGTH])
/* Only add this governor if it is unique */
for (int j = 0; j < num_governors; j++) {
if (strncmp(fullpath, governors[i], MAX_GOVERNOR_LENGTH) == 0) {
if (strncmp(fullpath, governors[i], PATH_MAX) == 0) {
continue;
}
}
/* Copy this governor into the output set */
static_assert(MAX_GOVERNOR_LENGTH > PATH_MAX, "possible string truncation");
strncpy(governors[num_governors], fullpath, MAX_GOVERNOR_LENGTH);
num_governors++;
}
@ -122,21 +122,28 @@ const char *get_gov_state(void)
long length = ftell(f);
fseek(f, 0, SEEK_SET);
char contents[length];
if (fread(contents, 1, (size_t)length, f) > 0) {
/* Files have a newline */
strtok(contents, "\n");
if (strlen(governor) > 0 && strncmp(governor, contents, 64) != 0) {
/* Don't handle the mixed case, this shouldn't ever happen
* But it is a clear sign we shouldn't carry on */
LOG_ERROR("Governors malformed: got \"%s\", expected \"%s\"", contents, governor);
return "malformed";
}
strncpy(governor, contents, sizeof(governor));
if (length == -1) {
LOG_ERROR("Failed to seek file %s\n", gov);
} else {
LOG_ERROR("Failed to read contents of %s\n", gov);
char contents[length];
if (fread(contents, 1, (size_t)length, f) > 0) {
/* Files have a newline */
strtok(contents, "\n");
if (strlen(governor) > 0 && strncmp(governor, contents, 64) != 0) {
/* Don't handle the mixed case, this shouldn't ever happen
* But it is a clear sign we shouldn't carry on */
LOG_ERROR("Governors malformed: got \"%s\", expected \"%s\"",
contents,
governor);
fclose(f);
return "malformed";
}
strncpy(governor, contents, sizeof(governor) - 1);
} else {
LOG_ERROR("Failed to read contents of %s\n", gov);
}
}
fclose(f);

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -28,10 +28,8 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "gpu-control.h"
#include "logging.h"
#include <stdio.h>
#include "common-gpu.h"
#include "common-logging.h"
/* Get the vendor for a device */
enum GPUVendor gamemode_get_gpu_vendor(long device)
@ -50,10 +48,13 @@ enum GPUVendor gamemode_get_gpu_vendor(long device)
return Vendor_Invalid;
}
char buff[64];
if (fgets(buff, 64, file) != NULL) {
bool got_line = fgets(buff, 64, file) != NULL;
fclose(file);
if (got_line) {
vendor = strtol(buff, NULL, 0);
} else {
LOG_ERROR("Coudn't read contents of file %s, will not apply optimisations!\n", path);
LOG_ERROR("Couldn't read contents of file %s, will not apply optimisations!\n", path);
return Vendor_Invalid;
}
@ -62,9 +63,9 @@ enum GPUVendor gamemode_get_gpu_vendor(long device)
LOG_ERROR("Unknown vendor value (0x%04x) found, cannot apply optimisations!\n",
(unsigned int)vendor);
LOG_ERROR("Known values are: 0x%04x (NVIDIA) 0x%04x (AMD) 0x%04x (Intel)\n",
Vendor_NVIDIA,
Vendor_AMD,
Vendor_Intel);
(unsigned int)Vendor_NVIDIA,
(unsigned int)Vendor_AMD,
(unsigned int)Vendor_Intel);
return Vendor_Invalid;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -30,7 +30,7 @@ POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include "daemon_config.h"
#define GPU_VALUE_MAX 256
/* Enums for GPU vendors */
enum GPUVendor {
@ -52,7 +52,7 @@ struct GameModeGPUInfo {
long nv_mem; /* Nvidia mem clock */
long nv_powermizer_mode; /* NV Powermizer Mode */
char amd_performance_level[CONFIG_VALUE_MAX]; /* The AMD performance level set to */
char amd_performance_level[GPU_VALUE_MAX]; /* The AMD performance level set to */
};
/* Get the vendor for a device */

42
common/common-helpers.c Normal file
View File

@ -0,0 +1,42 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
Copyright (c) 2019, Red Hat
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.
*/
#define _GNU_SOURCE
#include "common-helpers.h"
/* Starting with C99 we can use "inline" without "static" and thus avoid
* having multiple (local) definitions of the same inline function. One
* consequence of that is that if the compiler decides to *not* inline
* a specific call to the function the linker will expect an definition.
*/
extern inline void cleanup_close(int *fd);
extern inline void cleanup_free(void *ptr);

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,14 +31,15 @@ POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <unistd.h>
/**
* Value clamping helper, works like MIN/MAX but constraints a value within the range.
*/
#define CLAMP(lbound, ubound, value) MIN(MIN(lbound, ubound), MAX(MAX(lbound, ubound), value))
#define CLAMP(l, u, value) MAX(MIN(l, u), MIN(MAX(l, u), value))
/**
* Little helper to safely print into a buffer, returns a pointer into the buffer
@ -62,3 +63,41 @@ static inline const char *strtail(const char *haystack, const char *needle)
return pos;
return NULL;
}
/**
* Helper function for autoclosing file-descriptors. Does nothing if the argument
* is NULL or the referenced integer < 0.
*/
inline void cleanup_close(int *fd_ptr)
{
if (fd_ptr == NULL || *fd_ptr < 0)
return;
(void)close(*fd_ptr);
}
/**
* Helper macro for autoclosing file-descriptors: use by prefixing the variable,
* like "autoclose_fd int fd = -1;".
*/
#define autoclose_fd __attribute__((cleanup(cleanup_close)))
/**
* Helper function for auto-freeing dynamically allocated memory. Does nothing
* if *ptr is NULL (ptr must not be NULL).
*/
inline void cleanup_free(void *ptr)
{
/* The function is defined to work with 'void *' because
* that will make sure it compiles without warning also
* for all types; what we are getting passed into is a
* pointer to a pointer though, so we need to cast */
void *target = *(void **)ptr;
free(target); /* free can deal with NULL */
}
/**
* Helper macro for auto-freeing dynamically allocated memory: use by
* prefixing the variable, like "autofree char *data = NULL;".
*/
#define autofree __attribute__((cleanup(cleanup_free)))

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -29,7 +29,8 @@ POSSIBILITY OF SUCH DAMAGE.
*/
#include "logging.h"
#include "common-logging.h"
#include "syslog.h"
static bool use_syslog = false;

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -37,7 +37,6 @@ POSSIBILITY OF SUCH DAMAGE.
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
/* Macros to help with basic logging */
#define PLOG_MSG(...) printf(__VA_ARGS__)

202
common/common-pidfds.c Normal file
View File

@ -0,0 +1,202 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
Copyright (c) 2019, Red Hat
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.
*/
#define _GNU_SOURCE
#include <build-config.h>
#include "common-helpers.h"
#include "common-pidfds.h"
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#if !HAVE_FN_PIDFD_OPEN
#include <sys/syscall.h>
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434
#endif
static int pidfd_open(pid_t pid, unsigned int flags)
{
return (int)syscall(__NR_pidfd_open, pid, flags);
}
#else
#include <sys/pidfd.h>
#endif
/* pidfd functions */
int open_pidfds(pid_t *pids, int *fds, int count)
{
int i = 0;
for (i = 0; i < count; i++) {
int pid = pids[i];
int fd = pidfd_open(pid, 0);
if (fd < 0)
break;
fds[i] = fd;
}
return i;
}
static int parse_pid(const char *str, pid_t *pid)
{
unsigned long long int v;
char *end;
pid_t p;
errno = 0;
v = strtoull(str, &end, 0);
if (end == str)
return -ENOENT;
else if (errno != 0)
return -errno;
p = (pid_t)v;
if (p < 1 || (unsigned long long int)p != v)
return -ERANGE;
if (pid)
*pid = p;
return 0;
}
static int parse_status_field_pid(const char *val, pid_t *pid)
{
const char *t;
t = strrchr(val, '\t');
if (t == NULL)
return -ENOENT;
return parse_pid(t, pid);
}
static int pidfd_to_pid(int fdinfo, int pidfd, pid_t *pid)
{
autofree char *key = NULL;
autofree char *val = NULL;
char name[256] = {
0,
};
bool found = false;
FILE *f = NULL;
size_t keylen = 0;
size_t vallen = 0;
ssize_t n;
int fd;
int r = 0;
*pid = 0;
buffered_snprintf(name, "%d", pidfd);
fd = openat(fdinfo, name, O_RDONLY | O_CLOEXEC | O_NOCTTY);
if (fd != -1)
f = fdopen(fd, "r");
if (f == NULL)
return -errno;
do {
n = getdelim(&key, &keylen, ':', f);
if (n == -1) {
r = errno;
break;
}
n = getdelim(&val, &vallen, '\n', f);
if (n == -1) {
r = errno;
break;
}
// TODO: strstrip (key);
if (!strncmp(key, "Pid", 3)) {
r = parse_status_field_pid(val, pid);
found = r > -1;
}
} while (r == 0 && !found);
fclose(f);
if (r < 0)
return r;
else if (!found)
return -ENOENT;
return 0;
}
int pidfds_to_pids(int *fds, pid_t *pids, int count)
{
int fdinfo = -1;
int r = 0;
int i;
fdinfo = open_fdinfo_dir();
if (fdinfo == -1)
return -1;
for (i = 0; i < count && r == 0; i++)
r = pidfd_to_pid(fdinfo, fds[i], &pids[i]);
(void)close(fdinfo);
if (r != 0)
errno = -r;
return i;
}
/* misc directory helpers */
int open_fdinfo_dir(void)
{
return open("/proc/self/fdinfo", O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
}

View File

@ -1,6 +1,7 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
Copyright (c) 2019, Red Hat
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -27,55 +28,26 @@ 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.
*/
#include "daemonize.h"
#include "logging.h"
*/
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/**
* Helper to perform standard UNIX daemonization
*/
void daemonize(const char *name)
{
/* Initial fork */
pid_t pid = fork();
if (pid < 0) {
FATAL_ERRORNO("Failed to fork");
}
/* Open pidfds for up to count process ids specified in pids. The
* pointer fds needs to point to an array with at least count
* entries. Will stop when it encounters an error (and sets errno).
* Returns the number of successfully opened pidfds (or -1 in case
* of other errors. */
int open_pidfds(pid_t *pids, int *fds, int count);
if (pid != 0) {
LOG_MSG("Daemon launched as %s...\n", name);
exit(EXIT_SUCCESS);
}
/* Translate up to count process ids to the corresponding process ids.
* The pointer pids needs to point to an array with at least count
* entries. Will stop when it encounters an error (and sets errno).
* Returns the number of successfully translated pidfds (or -1 in
* case of other errors. */
int pidfds_to_pids(int *fds, pid_t *pids, int count);
/* Fork a second time */
pid = fork();
if (pid < 0) {
FATAL_ERRORNO("Failed to fork");
} else if (pid > 0) {
exit(EXIT_SUCCESS);
}
/* Now continue execution */
umask(0022);
if (setsid() < 0) {
FATAL_ERRORNO("Failed to create process group\n");
}
if (chdir("/") < 0) {
FATAL_ERRORNO("Failed to change to root directory\n");
}
/* replace standard file descriptors by /dev/null */
int devnull_r = open("/dev/null", O_RDONLY);
int devnull_w = open("/dev/null", O_WRONLY);
dup2(devnull_r, STDIN_FILENO);
dup2(devnull_w, STDOUT_FILENO);
dup2(devnull_w, STDERR_FILENO);
close(devnull_r);
close(devnull_w);
}
/* Helper to open the fdinfo directory for the current process, i.e.
* does open("/proc/self/fdinfo", ...). Returns the file descriptor
* for the directory, ownership is transferred and caller needs to
* call close on it. */
int open_fdinfo_dir(void);

165
common/common-power.c Normal file
View File

@ -0,0 +1,165 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
Copyright (c) 2019, Intel Corporation
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.
*/
#define _GNU_SOURCE
#include "common-power.h"
#include "common-logging.h"
#include <linux/limits.h>
#include <assert.h>
#include <ctype.h>
#include <glob.h>
#include <stdio.h>
#include <string.h>
static bool read_file_in_dir(const char *dir, const char *file, char *dest, size_t n)
{
char path[PATH_MAX];
int ret = snprintf(path, sizeof(path), "%s/%s", dir, file);
if (ret < 0 || ret >= (int)sizeof(path)) {
LOG_ERROR("Path length overrun");
return false;
}
FILE *f = fopen(path, "r");
if (!f) {
LOG_ERROR("Failed to open file for read %s\n", path);
return false;
}
size_t read = fread(dest, 1, n, f);
/* Close before we do any error checking */
fclose(f);
if (read <= 0) {
LOG_ERROR("Failed to read contents of %s: (%s)\n", path, strerror(errno));
return false;
}
if (read >= n) {
LOG_ERROR("File contained more data than expected %s\n", path);
return false;
}
/* Ensure we're null terminated */
dest[read] = '\0';
/* Trim whitespace off the end */
while (read > 0 && isspace(dest[read - 1])) {
dest[read - 1] = '\0';
read--;
}
return true;
}
static bool get_energy_uj(const char *rapl_name, uint32_t *energy_uj)
{
glob_t glo = { 0 };
static const char *path = "/sys/class/powercap/intel-rapl/intel-rapl:0/intel-rapl:0:*";
/* Assert some sanity on this glob */
if (glob(path, GLOB_NOSORT, NULL, &glo) != 0) {
LOG_ERROR("glob failed for RAPL paths: (%s)\n", strerror(errno));
return false;
}
/* If the glob doesn't find anything, this most likely means we don't
* have an Intel CPU or we have a kernel which does not support RAPL on
* our CPU.
*/
if (glo.gl_pathc < 1) {
LOG_ONCE(MSG,
"Intel RAPL interface not found in sysfs. "
"This is only problematic if you expected Intel iGPU "
"power threshold optimization.");
globfree(&glo);
return false;
}
/* Walk the glob set */
for (size_t i = 0; i < glo.gl_pathc; i++) {
char name[32];
if (!read_file_in_dir(glo.gl_pathv[i], "name", name, sizeof(name))) {
return false;
}
/* We're searching for the directory where the file named "name"
* contains the contents rapl_name. */
if (strncmp(name, rapl_name, sizeof(name)) != 0) {
continue;
}
char energy_uj_str[32];
if (!read_file_in_dir(glo.gl_pathv[i], "energy_uj", energy_uj_str, sizeof(energy_uj_str))) {
return false;
}
char *end = NULL;
long long energy_uj_ll = strtoll(energy_uj_str, &end, 10);
if (end == energy_uj_str) {
LOG_ERROR("Invalid energy_uj contents: %s\n", energy_uj_str);
return false;
}
if (energy_uj_ll < 0) {
LOG_ERROR("Value of energy_uj is out of expected bounds: %lld\n", energy_uj_ll);
return false;
}
/* Go ahead and clamp to 32 bits. We assume 32 bits later when
* taking deltas and wrapping at 32 bits is exactly what the Linux
* kernel's turbostat utility does so it's probably right.
*/
*energy_uj = (uint32_t)energy_uj_ll;
return true;
}
/* If we got here then the CPU and Kernel support RAPL and all our file
* access has succeeded but we failed to find an entry with the right
* name. This most likely means we're asking for "uncore" but are on a
* machine that doesn't have an integrated GPU.
*/
return false;
}
bool get_cpu_energy_uj(uint32_t *energy_uj)
{
return get_energy_uj("core", energy_uj);
}
bool get_igpu_energy_uj(uint32_t *energy_uj)
{
return get_energy_uj("uncore", energy_uj);
}

45
common/common-power.h Normal file
View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
#pragma once
#include <stdbool.h>
#include <stdint.h>
/**
* Get the amount of energy used to date by the CPU in microjoules
*/
bool get_cpu_energy_uj(uint32_t *energy_uj);
/**
* Get the amount of energy used to date by the integrated GPU in microjoules
*/
bool get_igpu_energy_uj(uint32_t *energy_uj);

86
common/common-profile.c Normal file
View File

@ -0,0 +1,86 @@
/*
Copyright (c) 2025, the GameMode contributors
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.
*/
#define _GNU_SOURCE
#include "common-profile.h"
#include "common-logging.h"
/**
* Path for platform profile
*/
const char *profile_path = "/sys/firmware/acpi/platform_profile";
/**
* Check if platform profile file exists
*/
int profile_exists(void)
{
return !access(profile_path, F_OK);
}
/**
* Return the current platform profile state
*/
const char *get_profile_state(void)
{
/* Persistent profile state */
static char profile[64] = { 0 };
memset(profile, 0, sizeof(profile));
FILE *f = fopen(profile_path, "r");
if (!f) {
LOG_ERROR("Failed to open file for read %s\n", profile_path);
return "none";
}
/* Grab the file length */
fseek(f, 0, SEEK_END);
long length = ftell(f);
fseek(f, 0, SEEK_SET);
if (length == -1) {
LOG_ERROR("Failed to seek file %s\n", profile_path);
} else {
char contents[length + 1];
if (fread(contents, 1, (size_t)length, f) > 0) {
strtok(contents, "\n");
strncpy(profile, contents, sizeof(profile) - 1);
} else {
LOG_ERROR("Failed to read contents of %s\n", profile_path);
}
}
fclose(f);
return profile;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2025, the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,16 +31,20 @@ POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <stdbool.h>
#include "gamemode.h"
#include <linux/limits.h>
#include <unistd.h>
/**
* Run the main D-BUS loop "forever"
* Path for platform profile
*/
void game_mode_context_loop(GameModeContext *context);
extern const char *profile_path;
/**
* Inhibit the screensaver
* Check if platform profile file exists
*/
int game_mode_inhibit_screensaver(bool inhibit);
int profile_exists(void);
/**
* Get the current platform profile state
*/
const char *get_profile_state(void);

64
common/common-splitlock.c Normal file
View File

@ -0,0 +1,64 @@
/*
Copyright (c) 2025, the GameMode contributors
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.
*/
#define _GNU_SOURCE
#include "common-splitlock.h"
#include "common-logging.h"
/**
* Path for the split lock mitigation state
*/
const char *splitlock_path = "/proc/sys/kernel/split_lock_mitigate";
/**
* Return the current split lock mitigation state
*/
long get_splitlock_state(void)
{
FILE *f = fopen(splitlock_path, "r");
if (!f) {
LOG_ERROR("Failed to open file for read %s\n", splitlock_path);
return -1;
}
char contents[41] = { 0 };
long value = -1;
if (fread(contents, 1, sizeof contents - 1, f) > 0) {
value = strtol(contents, NULL, 10);
} else {
LOG_ERROR("Failed to read contents of %s\n", splitlock_path);
}
fclose(f);
return value;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2025, the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,8 +31,14 @@ POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <linux/limits.h>
/**
* Attempt daemonization of the process.
* If this fails, the process will exit
* Path for the split lock mitagation state
*/
void daemonize(const char *name);
extern const char *splitlock_path;
/**
* Get the current split lock mitigation state
*/
long get_splitlock_state(void);

40
common/meson.build Normal file
View File

@ -0,0 +1,40 @@
# Convenience library for the duplicated logging functionality
common_sources = [
'common-logging.c',
'common-governors.c',
'common-profile.c',
'common-splitlock.c',
'common-external.c',
'common-helpers.c',
'common-gpu.c',
'common-cpu.c',
'common-pidfds.c',
'common-power.c',
]
daemon_common = static_library(
'daemon-common',
sources: common_sources,
install: false,
include_directories: [config_h_dir]
)
link_daemon_common = declare_dependency(
link_with: daemon_common,
include_directories: [include_directories('.')]
)
lib_common = static_library(
'lib-common',
sources: [
'common-helpers.c',
'common-pidfds.c'
],
install: false,
include_directories: [config_h_dir]
)
link_lib_common = declare_dependency(
link_with: lib_common,
include_directories: [include_directories('.')]
)

49
daemon/README.md Normal file
View File

@ -0,0 +1,49 @@
### gamemoded
**gamemoded** is a daemon that runs in the background, activates system and program optimisations on request, refcounts and also checks caller lifetime.
**gamemoded** currently supports the current arguments:
```
Usage: gamemoded [-d] [-l] [-r] [-t] [-h] [-v]
-r[PID], --request=[PID] Toggle gamemode for process
When no PID given, requests gamemode and pauses
-s[PID], --status=[PID] Query the status of gamemode for process
When no PID given, queries the status globally
-d, --daemonize Daemonize self after launch
-l, --log-to-syslog Log to syslog
-t, --test Run tests
-h, --help Print this help
-v, --version Print version
```
Run `man gamemoded` for information and options.
---
## Daemon Features
### Scheduling
GameMode can leverage support for soft real time mode if the running kernel supports `SCHED_ISO` (not currently supported in upstream kernels), controlled by the `softrealtime` option. This adjusts the scheduling of the game to real time without sacrificing system stability by starving other processes.
GameMode can adjust the nice priority of games to give them a slight IO and CPU priority over other background processes, controlled by the `renice` option. This only works if your user is permitted to adjust priorities within the limits configured by PAM. GameMode can be configured to take care of it by passing `with-pam-group=group` to the build options where `group` is a group your user needs to be part of.
For more information, see `/etc/security/limits.conf`.
Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage across all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). This value also protects against compromising system stability, do not set it to 100% as this would turn the game into a full real time process, thus potentially starving all other OS components from CPU resources.
### IO priority
GameMode can adjust the I/O priority of games to benefit from reduced lag and latency when a game has to load assets on demand. This is done by default.
### For those with overclocked CPUs
If you have an AMD CPU and have disabled Cool'n'Quiet, or you have an Intel CPU and have disabled SpeedStep, then GameMode's governor settings will not work, as your CPU is not running with a governor. You are already getting maximum performance.
If you are unsure, `bootstrap.sh` will warn you if your system lacks CPU governor control.
Scripts and other features will still work.
### GPU optimisations
GameMode is able to automatically apply GPU performance mode changes on AMD and NVIDIA, and overclocking on NVIDIA, when activated. AMD support currently requires the `amdgpu` kernel module, and NVIDIA requires the `coolbits` extension to be enabled in the NVIDIA settings.
It is very much encouraged for users to find out their own overclocking limits manually before venturing into configuring them in GameMode, and activating this feature in GameMode assumes you take responsibility for the effects of said overclocks.
More information can be found in the `example/gamemode.ini` file.
Note that both NVIDIA (GPUBoost) and AMD (Overdrive) devices and drivers already attempt to internally overclock if possible, but it is still common for enthusiasts to want to manually push the upper threshold.

View File

@ -1,334 +0,0 @@
/*
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.
*/
#define _GNU_SOURCE
#include "dbus_messaging.h"
#include "daemonize.h"
#include "gamemode.h"
#include "logging.h"
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-daemon.h>
/* systemd dbus components */
static sd_bus *bus = NULL;
static sd_bus_slot *slot = NULL;
/**
* Clean up our private dbus state
*/
static void clean_up(void)
{
if (slot) {
sd_bus_slot_unref(slot);
}
slot = NULL;
if (bus) {
sd_bus_unref(bus);
}
bus = NULL;
}
/**
* Handles the RegisterGame D-BUS Method
*/
static int method_register_game(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int pid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "i", &pid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_register(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the UnregisterGame D-BUS Method
*/
static int method_unregister_game(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int pid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "i", &pid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_unregister(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the QueryStatus D-BUS Method
*/
static int method_query_status(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int pid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "i", &pid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_query_status(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the RegisterGameByPID D-BUS Method
*/
static int method_register_game_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = game_mode_context_register(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the UnregisterGameByPID D-BUS Method
*/
static int method_unregister_game_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = game_mode_context_unregister(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the QueryStatus D-BUS Method
*/
static int method_query_status_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_query_status(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* D-BUS vtable to dispatch virtual methods
*/
static const sd_bus_vtable gamemode_vtable[] =
{ SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("RegisterGame", "i", "i", method_register_game, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGame", "i", "i", method_unregister_game, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatus", "i", "i", method_query_status, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RegisterGameByPID", "ii", "i", method_register_game_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGameByPID", "ii", "i", method_unregister_game_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatusByPID", "ii", "i", method_query_status_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_VTABLE_END };
/**
* Main process loop for the daemon. Run until quitting has been requested.
*/
void game_mode_context_loop(GameModeContext *context)
{
/* Set up function to handle clean up of resources */
atexit(clean_up);
int ret = 0;
/* Connect to the session bus */
ret = sd_bus_open_user(&bus);
if (ret < 0) {
FATAL_ERROR("Failed to connect to the bus: %s\n", strerror(-ret));
}
/* Create the object to allow connections */
ret = sd_bus_add_object_vtable(bus,
&slot,
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
gamemode_vtable,
context);
if (ret < 0) {
FATAL_ERROR("Failed to install GameMode object: %s\n", strerror(-ret));
}
/* Request our name */
ret = sd_bus_request_name(bus, "com.feralinteractive.GameMode", 0);
if (ret < 0) {
FATAL_ERROR("Failed to acquire service name: %s\n", strerror(-ret));
}
LOG_MSG("Successfully initialised bus with name [%s]...\n", "com.feralinteractive.GameMode");
sd_notifyf(0, "STATUS=%sGameMode is ready to be activated.%s\n", "\x1B[1;36m", "\x1B[0m");
/* Now loop, waiting for callbacks */
for (;;) {
ret = sd_bus_process(bus, NULL);
if (ret < 0) {
FATAL_ERROR("Failure when processing the bus: %s\n", strerror(-ret));
}
/* We're done processing */
if (ret > 0) {
continue;
}
/* Wait for more */
ret = sd_bus_wait(bus, (uint64_t)-1);
if (ret < 0 && -ret != EINTR) {
FATAL_ERROR("Failure when waiting on bus: %s\n", strerror(-ret));
}
}
}
/**
* Attempts to inhibit the screensaver
* Uses the "org.freedesktop.ScreenSaver" interface
*/
static unsigned int screensaver_inhibit_cookie = 0;
int game_mode_inhibit_screensaver(bool inhibit)
{
const char *service = "org.freedesktop.ScreenSaver";
const char *object_path = "/org/freedesktop/ScreenSaver";
const char *interface = "org.freedesktop.ScreenSaver";
const char *function = inhibit ? "Inhibit" : "UnInhibit";
sd_bus_message *msg = NULL;
sd_bus *bus = NULL;
sd_bus_error err;
memset(&err, 0, sizeof(sd_bus_error));
int result = -1;
// Open the user bus
int ret = sd_bus_open_user(&bus);
if (ret < 0) {
LOG_ERROR("Could not connect to user bus: %s\n", strerror(-ret));
return -1;
}
if (inhibit) {
ret = sd_bus_call_method(bus,
service,
object_path,
interface,
function,
&err,
&msg,
"ss",
"com.feralinteractive.GameMode",
"GameMode Activated");
} else {
ret = sd_bus_call_method(bus,
service,
object_path,
interface,
function,
&err,
&msg,
"u",
screensaver_inhibit_cookie);
}
if (ret < 0) {
LOG_ERROR(
"Could not call %s on %s: %s\n"
"\t%s\n"
"\t%s\n",
function,
service,
strerror(-ret),
err.name,
err.message);
} else if (inhibit) {
// Read the reply
ret = sd_bus_message_read(msg, "u", &screensaver_inhibit_cookie);
if (ret < 0) {
LOG_ERROR("Failure to parse response from %s on %s: %s\n",
function,
service,
strerror(-ret));
} else {
result = 0;
}
} else {
result = 0;
}
return result;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -30,18 +30,23 @@ POSSIBILITY OF SUCH DAMAGE.
*/
#define _GNU_SOURCE
#include "daemon_config.h"
#include "logging.h"
#include "gamemode-config.h"
#include "common-helpers.h"
#include "common-logging.h"
#include "build-config.h"
/* Ben Hoyt's inih library */
#include "ini.h"
#include <ini.h>
#include <linux/limits.h>
#include <dirent.h>
#include <libgen.h>
#include <math.h>
#include <pthread.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <sys/stat.h>
/* Name and possible location of the config file */
#define CONFIG_NAME "gamemode.ini"
@ -49,6 +54,8 @@ POSSIBILITY OF SUCH DAMAGE.
/* Default value for the reaper frequency */
#define DEFAULT_REAPER_FREQ 5
#define DEFAULT_IGPU_POWER_THRESHOLD 0.3f
/* Helper macro for defining the config variable getter */
#define DEFINE_CONFIG_GET(name) \
long config_get_##name(GameModeConfig *self) \
@ -58,6 +65,9 @@ POSSIBILITY OF SUCH DAMAGE.
return value; \
}
/* The number of current locations for config files */
#define CONFIG_NUM_LOCATIONS 4
/**
* The config holds various details as needed
* and a rwlock to allow config_reload to be called
@ -65,7 +75,7 @@ POSSIBILITY OF SUCH DAMAGE.
struct GameModeConfig {
pthread_rwlock_t rwlock;
int inotfd;
int inotwd;
int inotwd[CONFIG_NUM_LOCATIONS];
struct {
char whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
@ -78,6 +88,12 @@ struct GameModeConfig {
char defaultgov[CONFIG_VALUE_MAX];
char desiredgov[CONFIG_VALUE_MAX];
char defaultprof[CONFIG_VALUE_MAX];
char desiredprof[CONFIG_VALUE_MAX];
char igpu_desiredgov[CONFIG_VALUE_MAX];
float igpu_power_threshold;
char softrealtime[CONFIG_VALUE_MAX];
long renice;
@ -85,6 +101,8 @@ struct GameModeConfig {
long inhibit_screensaver;
long disable_splitlock;
long reaper_frequency;
char apply_gpu_optimisations[CONFIG_VALUE_MAX];
@ -94,6 +112,9 @@ struct GameModeConfig {
long nv_powermizer_mode;
char amd_performance_level[CONFIG_VALUE_MAX];
char cpu_park_cores[CONFIG_VALUE_MAX];
char cpu_pin_cores[CONFIG_VALUE_MAX];
long require_supervisor;
char supervisor_whitelist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
char supervisor_blacklist[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
@ -175,6 +196,27 @@ __attribute__((unused)) static bool get_long_value_hex(const char *value_name, c
return true;
}
/*
* Get a long value from a string
*/
static bool get_float_value(const char *value_name, const char *value, float *output)
{
char *end = NULL;
float config_value = strtof(value, &end);
if (errno == ERANGE) {
LOG_ERROR("Config: %s overflowed, given [%s]\n", value_name, value);
return false;
} else if (!(*value != '\0' && end && *end == '\0')) {
LOG_ERROR("Config: %s was invalid, given [%s]\n", value_name, value);
return false;
} else {
*output = config_value;
}
return true;
}
/**
* Simple strstr scheck
* Could be expanded for wildcard or regex
@ -226,6 +268,14 @@ static int inih_handler(void *user, const char *section, const char *name, const
valid = get_string_value(value, self->values.defaultgov);
} else if (strcmp(name, "desiredgov") == 0) {
valid = get_string_value(value, self->values.desiredgov);
} else if (strcmp(name, "defaultprof") == 0) {
valid = get_string_value(value, self->values.defaultprof);
} else if (strcmp(name, "desiredprof") == 0) {
valid = get_string_value(value, self->values.desiredprof);
} else if (strcmp(name, "igpu_desiredgov") == 0) {
valid = get_string_value(value, self->values.igpu_desiredgov);
} else if (strcmp(name, "igpu_power_threshold") == 0) {
valid = get_float_value(name, value, &self->values.igpu_power_threshold);
} else if (strcmp(name, "softrealtime") == 0) {
valid = get_string_value(value, self->values.softrealtime);
} else if (strcmp(name, "renice") == 0) {
@ -234,6 +284,8 @@ static int inih_handler(void *user, const char *section, const char *name, const
valid = get_string_value(value, self->values.ioprio);
} else if (strcmp(name, "inhibit_screensaver") == 0) {
valid = get_long_value(name, value, &self->values.inhibit_screensaver);
} else if (strcmp(name, "disable_splitlock") == 0) {
valid = get_long_value(name, value, &self->values.disable_splitlock);
}
} else if (strcmp(section, "gpu") == 0) {
/* Protect the user - don't allow these config options from unsafe config locations */
@ -242,9 +294,7 @@ static int inih_handler(void *user, const char *section, const char *name, const
"The [gpu] config section is not configurable from unsafe config files! Option %s "
"will be ignored!\n",
name);
LOG_ERROR(
"Consider moving this option to /etc/gamemode.ini or "
"/usr/share/gamemode/gamemode.ini\n");
LOG_ERROR("Consider moving this option to /etc/gamemode.ini\n");
}
/* GPU subsection */
@ -261,6 +311,12 @@ static int inih_handler(void *user, const char *section, const char *name, const
} else if (strcmp(name, "amd_performance_level") == 0) {
valid = get_string_value(value, self->values.amd_performance_level);
}
} else if (strcmp(section, "cpu") == 0) {
if (strcmp(name, "park_cores") == 0) {
valid = get_string_value(value, self->values.cpu_park_cores);
} else if (strcmp(name, "pin_cores") == 0) {
valid = get_string_value(value, self->values.cpu_pin_cores);
}
} else if (strcmp(section, "supervisor") == 0) {
/* Supervisor subsection */
if (strcmp(name, "supervisor_whitelist") == 0) {
@ -325,9 +381,11 @@ static void load_config_files(GameModeConfig *self)
memset(&self->values, 0, sizeof(self->values));
/* Set some non-zero defaults */
self->values.igpu_power_threshold = DEFAULT_IGPU_POWER_THRESHOLD;
self->values.inhibit_screensaver = 1; /* Defaults to on */
self->values.disable_splitlock = 1; /* Defaults to on */
self->values.reaper_frequency = DEFAULT_REAPER_FREQ;
self->values.gpu_device = -1; /* 0 is a valid device ID so use -1 to indicate no value */
self->values.gpu_device = 0;
self->values.nv_powermizer_mode = -1;
self->values.nv_core_clock_mhz_offset = -1;
self->values.nv_mem_clock_mhz_offset = -1;
@ -341,19 +399,20 @@ static void load_config_files(GameModeConfig *self)
const char *path;
bool protected;
};
struct ConfigLocation locations[] = {
{ "/usr/share/gamemode", true }, /* shipped default config */
struct ConfigLocation locations[CONFIG_NUM_LOCATIONS] = {
{ SYSCONFDIR, true }, /* shipped default config */
{ "/etc", true }, /* administrator config */
{ config_location_home, false }, /* $XDG_CONFIG_HOME or $HOME/.config/ */
{ config_location_local, false } /* local data eg. $PWD */
};
/* Load each file in order and overwrite values */
for (unsigned int i = 0; i < sizeof(locations) / sizeof(locations[0]); i++) {
for (unsigned int i = 0; i < CONFIG_NUM_LOCATIONS; i++) {
char *path = NULL;
if (locations[i].path && asprintf(&path, "%s/" CONFIG_NAME, locations[i].path) > 0) {
FILE *f = fopen(path, "r");
if (f) {
FILE *f = NULL;
DIR *d = NULL;
if ((f = fopen(path, "r"))) {
LOG_MSG("Loading config file [%s]\n", path);
load_protected = locations[i].protected;
int error = ini_parse_file(f, inih_handler, (void *)self);
@ -362,6 +421,25 @@ static void load_config_files(GameModeConfig *self)
if (error) {
LOG_MSG("Failed to parse config file - error on line %d!\n", error);
}
fclose(f);
/* Register for inotify */
/* Watch for modification, deletion, moves, or attribute changes */
uint32_t fileflags = IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF;
if ((self->inotwd[i] = inotify_add_watch(self->inotfd, path, fileflags)) == -1) {
LOG_ERROR("Failed to watch %s, error: %s", path, strerror(errno));
}
} else if ((d = opendir(locations[i].path))) {
/* We didn't find a file, so we'll wait on the directory */
/* Notify if a file is created, or move to the directory, or if the directory itself
* is removed or moved away */
uint32_t dirflags = IN_CREATE | IN_MOVED_TO | IN_DELETE_SELF | IN_MOVE_SELF;
if ((self->inotwd[i] =
inotify_add_watch(self->inotfd, locations[i].path, dirflags)) == -1) {
LOG_ERROR("Failed to watch %s, error: %s", path, strerror(errno));
}
closedir(d);
}
free(path);
}
@ -407,16 +485,104 @@ void config_init(GameModeConfig *self)
{
pthread_rwlock_init(&self->rwlock, NULL);
self->inotfd = inotify_init1(IN_NONBLOCK);
if (self->inotfd == -1)
LOG_ERROR(
"inotify_init failed: %s, gamemode will not be able to watch config files for edits!\n",
strerror(errno));
for (unsigned int i = 0; i < CONFIG_NUM_LOCATIONS; i++) {
self->inotwd[i] = -1;
}
/* load the initial config */
load_config_files(self);
}
/*
* Destroy internal parts of config
*/
static void internal_destroy(GameModeConfig *self)
{
pthread_rwlock_destroy(&self->rwlock);
for (unsigned int i = 0; i < CONFIG_NUM_LOCATIONS; i++) {
if (self->inotwd[i] != -1) {
/* TODO: Error handle */
inotify_rm_watch(self->inotfd, self->inotwd[i]);
}
}
if (self->inotfd != -1)
close(self->inotfd);
}
/*
* Re-load the config file
*/
void config_reload(GameModeConfig *self)
{
load_config_files(self);
internal_destroy(self);
config_init(self);
}
/*
* Check if the config needs to be reloaded
*/
bool config_needs_reload(GameModeConfig *self)
{
bool need = false;
/* Take a read lock while we use the inotify fd */
pthread_rwlock_rdlock(&self->rwlock);
const size_t buflen = sizeof(struct inotify_event) + NAME_MAX + 1;
char buffer[buflen] __attribute__((aligned(__alignof__(struct inotify_event))));
ssize_t len = read(self->inotfd, buffer, buflen);
if (len == -1) {
/* EAGAIN is returned when there's nothing to read on a non-blocking fd */
if (errno != EAGAIN)
LOG_ERROR("Could not read inotify fd: %s\n", strerror(errno));
} else if (len > 0) {
/* Iterate over each event we've been given */
size_t i = 0;
while (i < (size_t)len) {
struct inotify_event *event = (struct inotify_event *)&buffer[i];
/* We have picked up an event and need to handle it */
if (event->mask & IN_ISDIR) {
/* If the event is a dir event we need to take a look */
if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF) {
/* The directory itself changed, trigger a reload */
need = true;
break;
}
} else {
/* When the event has a filename (ie. is from a dir watch), check the name */
if (event->len > 0) {
if (strncmp(basename(event->name), CONFIG_NAME, strlen(CONFIG_NAME)) == 0) {
/* This is a gamemode config file, trigger a reload */
need = true;
break;
}
} else {
/* Otherwise this is for one of our watches on a specific config file, so
* trigger the reload regardless */
need = true;
break;
}
}
i += sizeof(struct inotify_event) + event->len;
}
}
/* Return the read lock */
pthread_rwlock_unlock(&self->rwlock);
return need;
}
/*
@ -424,7 +590,7 @@ void config_reload(GameModeConfig *self)
*/
void config_destroy(GameModeConfig *self)
{
pthread_rwlock_destroy(&self->rwlock);
internal_destroy(self);
/* Finally, free the memory */
free(self);
@ -487,6 +653,16 @@ bool config_get_inhibit_screensaver(GameModeConfig *self)
return val == 1;
}
/*
* Gets the disable splitlock setting
*/
bool config_get_disable_splitlock(GameModeConfig *self)
{
long val;
memcpy_locked_config(self, &val, &self->values.disable_splitlock, sizeof(long));
return val == 1;
}
/*
* Get a set of scripts to call when gamemode starts
*/
@ -522,13 +698,58 @@ void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALU
}
/*
* Get the chosen desired governor
* Get the chosen desired platform profile
*/
void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self, governor, self->values.desiredgov, sizeof(self->values.desiredgov));
}
/*
* Get the chosen default platform profile
*/
void config_get_default_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self, profile, self->values.defaultprof, sizeof(self->values.defaultprof));
}
/*
* Get the chosen desired governor
*/
void config_get_desired_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self, profile, self->values.desiredprof, sizeof(self->values.desiredprof));
}
/*
* Get the chosen iGPU desired governor
*/
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self,
governor,
self->values.igpu_desiredgov,
sizeof(self->values.igpu_desiredgov));
}
/*
* Get the chosen iGPU power threshold
*/
float config_get_igpu_power_threshold(GameModeConfig *self)
{
float value = 0;
memcpy_locked_config(self, &value, &self->values.igpu_power_threshold, sizeof(float));
/* Validate the threshold value */
if (isnan(value) || value < 0) {
LOG_ONCE(ERROR,
"Configured iGPU power threshold value '%f' is invalid, ignoring iGPU default "
"governor.\n",
value);
value = FP_INFINITE;
}
return value;
}
/*
* Get the chosen soft realtime behavior
*/
@ -547,6 +768,11 @@ long config_get_renice_value(GameModeConfig *self)
{
long value = 0;
memcpy_locked_config(self, &value, &self->values.renice, sizeof(long));
/* Validate the renice value */
if ((value < 1 || value > 20) && value != 0) {
LOG_ONCE(ERROR, "Configured renice value '%ld' is invalid, will not renice.\n", value);
value = 0;
}
return value;
}
@ -558,12 +784,30 @@ long config_get_ioprio_value(GameModeConfig *self)
long value = 0;
char ioprio_value[CONFIG_VALUE_MAX] = { 0 };
memcpy_locked_config(self, ioprio_value, &self->values.ioprio, sizeof(self->values.ioprio));
/* account for special string values */
if (0 == strncmp(ioprio_value, "off", sizeof(self->values.ioprio)))
value = IOPRIO_DONT_SET;
else if (0 == strncmp(ioprio_value, "default", sizeof(self->values.ioprio)))
value = IOPRIO_RESET_DEFAULT;
else
value = atoi(ioprio_value);
/* Validate values */
if (IOPRIO_RESET_DEFAULT == value) {
LOG_ONCE(MSG, "IO priority will be reset to default behavior (based on CPU priority).\n");
value = 0;
} else {
/* maybe clamp the value */
long invalid_ioprio = value;
value = CLAMP(0, 7, value);
if (value != invalid_ioprio)
LOG_ONCE(ERROR,
"IO priority value %ld invalid, clamping to %ld\n",
invalid_ioprio,
value);
}
return value;
}
@ -598,6 +842,25 @@ void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VA
*/
DEFINE_CONFIG_GET(require_supervisor)
/*
* Get various config info for cpu optimisations
*/
void config_get_cpu_park_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self,
value,
&self->values.cpu_park_cores,
sizeof(self->values.cpu_park_cores));
}
void config_get_cpu_pin_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX])
{
memcpy_locked_config(self,
value,
&self->values.cpu_pin_cores,
sizeof(self->values.cpu_pin_cores));
}
/*
* Checks if the supervisor is whitelisted
*/

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -44,6 +44,7 @@ POSSIBILITY OF SUCH DAMAGE.
*/
#define IOPRIO_RESET_DEFAULT -1
#define IOPRIO_DONT_SET -2
#define IOPRIO_DEFAULT 4
/*
* Opaque config context type
@ -67,6 +68,11 @@ void config_init(GameModeConfig *self);
*/
void config_reload(GameModeConfig *self);
/*
* Check if the config has changed and will need a reload
*/
bool config_needs_reload(GameModeConfig *self);
/*
* Destroy a config
* Invalidates the config
@ -74,66 +80,36 @@ void config_reload(GameModeConfig *self);
void config_destroy(GameModeConfig *self);
/*
* Get if the client is in the whitelist
* returns false for an empty whitelist
* Get if the client is in the whitelist or blacklist
* config_get_client_whitelisted returns false for an empty whitelist
*/
bool config_get_client_whitelisted(GameModeConfig *self, const char *client);
/*
* Get if the client is in the blacklist
*/
bool config_get_client_blacklisted(GameModeConfig *self, const char *client);
/*
* Get the frequency (in seconds) for the reaper thread
*/
long config_get_reaper_frequency(GameModeConfig *self);
/*
* Get whether we want to inhibit the screensaver (defaults to true)
*/
bool config_get_inhibit_screensaver(GameModeConfig *self);
/*
* Get a set of scripts to call when gamemode starts
* Get the script sets to run at the start or end
*/
void config_get_gamemode_start_scripts(GameModeConfig *self,
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]);
/*
* Get a set of scripts to call when gamemode ends
*/
void config_get_gamemode_end_scripts(GameModeConfig *self,
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX]);
/*
* Get the script timout value
* Various get methods for config values
*/
long config_get_reaper_frequency(GameModeConfig *self);
bool config_get_inhibit_screensaver(GameModeConfig *self);
long config_get_script_timeout(GameModeConfig *self);
/*
* Get the chosen default governor
*/
void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
/*
* Get the chosen desired governor
*/
void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
/*
* Get the chosen soft realtime behavior
*/
void config_get_default_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX]);
void config_get_desired_profile(GameModeConfig *self, char profile[CONFIG_VALUE_MAX]);
void config_get_igpu_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]);
float config_get_igpu_power_threshold(GameModeConfig *self);
void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]);
/*
* Get the renice value
*/
long config_get_renice_value(GameModeConfig *self);
/*
* Get the ioprio value
*/
long config_get_ioprio_value(GameModeConfig *self);
bool config_get_disable_splitlock(GameModeConfig *self);
/*
* Get various config info for gpu optimisations
@ -145,6 +121,12 @@ long config_get_nv_mem_clock_mhz_offset(GameModeConfig *self);
long config_get_nv_powermizer_mode(GameModeConfig *self);
void config_get_amd_performance_level(GameModeConfig *self, char value[CONFIG_VALUE_MAX]);
/*
* Get various config info for cpu optimisations
*/
void config_get_cpu_park_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX]);
void config_get_cpu_pin_cores(GameModeConfig *self, char value[CONFIG_VALUE_MAX]);
/**
* Functions to get supervisor config permissions
*/

1215
daemon/gamemode-context.c Normal file

File diff suppressed because it is too large Load Diff

540
daemon/gamemode-cpu.c Normal file
View File

@ -0,0 +1,540 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
#define _GNU_SOURCE
#include <linux/limits.h>
#include <dirent.h>
#include <sched.h>
#include "common-cpu.h"
#include "common-external.h"
#include "common-helpers.h"
#include "common-logging.h"
#include "gamemode.h"
#include "gamemode-config.h"
#include "build-config.h"
static int read_small_file(char *path, char **buf, size_t *buflen)
{
FILE *f = fopen(path, "r");
if (!f) {
LOG_ERROR("Couldn't open file at %s : %s\n", path, strerror(errno));
return 0;
}
ssize_t nread = getline(buf, buflen, f);
if (nread == -1) {
LOG_ERROR("Couldn't read file at %s : %s\n", path, strerror(errno));
fclose(f);
return 0;
}
fclose(f);
while (nread > 0 && ((*buf)[nread - 1] == '\n' || (*buf)[nread - 1] == '\r'))
nread--;
(*buf)[nread] = '\0';
return 1;
}
static void set_online_from_list(char *cpulist, GameModeCPUInfo *info)
{
long from, to;
while ((cpulist = parse_cpulist(cpulist, &from, &to))) {
for (long cpu = from; cpu < to + 1; cpu++) {
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online);
}
}
}
static int check_pe_cores(char **buf, size_t *buflen, GameModeCPUInfo *info)
{
if (!read_small_file("/sys/devices/cpu_core/cpus", buf, buflen))
return 0;
LOG_MSG("found kernel support for checking P/E-cores\n");
long from, to;
char *list = *buf;
while ((list = parse_cpulist(list, &from, &to))) {
for (long cpu = from; cpu < to + 1; cpu++) {
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
}
}
if (CPU_EQUAL_S(CPU_ALLOC_SIZE(info->num_cpu), info->online, info->to_keep) ||
CPU_COUNT_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) == 0)
LOG_MSG("kernel did not indicate that this was an P/E-cores system\n");
return 1;
}
static int walk_sysfs(char *cpulist, char **buf, size_t *buflen, GameModeCPUInfo *info)
{
char path[PATH_MAX];
unsigned long long max_cache = 0, max_freq = 0;
long from, to;
cpu_set_t *freq_cores = CPU_ALLOC(info->num_cpu);
char *list = cpulist;
while ((list = parse_cpulist(list, &from, &to))) {
for (long cpu = from; cpu < to + 1; cpu++) {
/* check for L3 cache non-uniformity among the cores */
int ret =
snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/cache/index3/size", cpu);
if (ret > 0 && ret < PATH_MAX) {
if (read_small_file(path, buf, buflen)) {
char *endp;
unsigned long long cache_size = strtoull(*buf, &endp, 10);
if (*endp == 'K') {
cache_size *= 1024;
} else if (*endp == 'M') {
cache_size *= 1024 * 1024;
} else if (*endp == 'G') {
cache_size *= 1024 * 1024 * 1024;
} else if (*endp != '\0') {
LOG_MSG("cpu L3 cache size (%s) on core #%ld is silly\n", *buf, cpu);
cache_size = 0;
}
if (cache_size > max_cache) {
max_cache = cache_size;
CPU_ZERO_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
}
if (cache_size == max_cache)
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
}
}
/* check for frequency non-uniformity among the cores */
ret = snprintf(path,
PATH_MAX,
"/sys/devices/system/cpu/cpu%ld/cpufreq/cpuinfo_max_freq",
cpu);
if (ret > 0 && ret < PATH_MAX) {
if (read_small_file(path, buf, buflen)) {
unsigned long long freq = strtoull(*buf, NULL, 10);
unsigned long long cutoff = (freq * 10) / 100;
if (freq > max_freq) {
if (max_freq < freq - cutoff)
CPU_ZERO_S(CPU_ALLOC_SIZE(info->num_cpu), freq_cores);
max_freq = freq;
}
if (freq + cutoff >= max_freq)
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), freq_cores);
}
}
}
}
if (CPU_EQUAL_S(CPU_ALLOC_SIZE(info->num_cpu), info->online, info->to_keep) ||
CPU_COUNT_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) == 0) {
LOG_MSG("cpu L3 cache was uniform, this is not a x3D with multiple chiplets\n");
CPU_FREE(info->to_keep);
info->to_keep = freq_cores;
if (CPU_EQUAL_S(CPU_ALLOC_SIZE(info->num_cpu), info->online, info->to_keep) ||
CPU_COUNT_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) == 0)
LOG_MSG("cpu frequency was uniform, this is not a big.LITTLE type of system\n");
} else {
CPU_FREE(freq_cores);
}
return 1;
}
static int walk_string(char *cpulist, char *config_cpulist, GameModeCPUInfo *info)
{
long from, to;
char *list = cpulist;
while ((list = parse_cpulist(list, &from, &to))) {
for (long cpu = from; cpu < to + 1; cpu++) {
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online);
if (info->park_or_pin == IS_CPU_PARK)
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
}
}
list = config_cpulist;
while ((list = parse_cpulist(list, &from, &to))) {
for (long cpu = from; cpu < to + 1; cpu++) {
if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online)) {
if (info->park_or_pin == IS_CPU_PARK)
CPU_CLR_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
else
CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep);
}
}
}
return 1;
}
void game_mode_reconfig_cpu(GameModeConfig *config, GameModeCPUInfo **info)
{
game_mode_unpark_cpu(*info);
game_mode_free_cpu(info);
game_mode_initialise_cpu(config, info);
}
int game_mode_initialise_cpu(GameModeConfig *config, GameModeCPUInfo **info)
{
/* Verify input, this is programmer error */
if (!info || *info)
FATAL_ERROR("Invalid GameModeCPUInfo passed to %s", __func__);
/* Early out if we have this feature turned off */
char park_cores[CONFIG_VALUE_MAX];
char pin_cores[CONFIG_VALUE_MAX];
config_get_cpu_park_cores(config, park_cores);
config_get_cpu_pin_cores(config, pin_cores);
int park_or_pin = -1;
if (pin_cores[0] != '\0') {
if (strcasecmp(pin_cores, "no") == 0 || strcasecmp(pin_cores, "false") == 0 ||
strcmp(pin_cores, "0") == 0) {
park_or_pin = -2;
} else if (strcasecmp(pin_cores, "yes") == 0 || strcasecmp(pin_cores, "true") == 0 ||
strcmp(pin_cores, "1") == 0) {
pin_cores[0] = '\0';
park_or_pin = IS_CPU_PIN;
} else {
park_or_pin = IS_CPU_PIN;
}
}
if (park_or_pin != IS_CPU_PIN && park_cores[0] != '\0') {
if (strcasecmp(park_cores, "no") == 0 || strcasecmp(park_cores, "false") == 0 ||
strcmp(park_cores, "0") == 0) {
if (park_or_pin == -2)
return 0;
park_or_pin = -1;
} else if (strcasecmp(park_cores, "yes") == 0 || strcasecmp(park_cores, "true") == 0 ||
strcmp(park_cores, "1") == 0) {
park_cores[0] = '\0';
park_or_pin = IS_CPU_PARK;
} else {
park_or_pin = IS_CPU_PARK;
}
}
/* always default to pin */
if (park_or_pin != IS_CPU_PARK)
park_or_pin = IS_CPU_PIN;
char *buf = NULL, *buf2 = NULL;
size_t buflen = 0, buf2len = 0;
/* first we find which cores are online, this also helps us to determine the max
* cpu core number that we need to allocate the cpulist later */
if (!read_small_file("/sys/devices/system/cpu/online", &buf, &buflen))
goto error_exit;
long from, to, max = 0;
char *s = buf;
while ((s = parse_cpulist(s, &from, &to))) {
if (to > max)
max = to;
}
/* either parsing failed or we have only a single core, in either case
* we cannot optimize anyway */
if (max == 0)
goto early_exit;
GameModeCPUInfo *new_info = malloc(sizeof(GameModeCPUInfo));
memset(new_info, 0, sizeof(GameModeCPUInfo));
new_info->num_cpu = (size_t)(max + 1);
new_info->park_or_pin = park_or_pin;
new_info->online = CPU_ALLOC(new_info->num_cpu);
new_info->to_keep = CPU_ALLOC(new_info->num_cpu);
CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online);
CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep);
if (park_or_pin == IS_CPU_PARK && park_cores[0] != '\0') {
if (!walk_string(buf, park_cores, new_info))
goto error_exit;
} else if (park_or_pin == IS_CPU_PIN && pin_cores[0] != '\0') {
if (!walk_string(buf, pin_cores, new_info))
goto error_exit;
} else {
set_online_from_list(buf, new_info);
if (!check_pe_cores(&buf2, &buf2len, new_info)) {
if (!walk_sysfs(buf, &buf2, &buf2len, new_info))
goto error_exit;
}
}
if (park_or_pin == IS_CPU_PARK &&
CPU_EQUAL_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online, new_info->to_keep)) {
game_mode_free_cpu(&new_info);
LOG_MSG("I can find no reason to perform core parking on this system!\n");
goto error_exit;
}
if (CPU_COUNT_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep) == 0) {
game_mode_free_cpu(&new_info);
LOG_MSG("I can find no reason to perform core pinning on this system!\n");
goto error_exit;
}
if (CPU_COUNT_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep) < 4) {
game_mode_free_cpu(&new_info);
LOG_MSG(
"logic or config would result in less than 4 active cores, will not apply cpu core "
"parking/pinning!\n");
goto error_exit;
}
*info = new_info;
early_exit:
free(buf);
free(buf2);
return 0;
error_exit:
free(buf);
free(buf2);
return -1;
}
static int log_state(char *cpulist, int *pos, const long first, const long last)
{
int ret;
if (*pos != 0) {
ret = snprintf(cpulist + *pos, ARG_MAX - (size_t)*pos, ",");
if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) {
LOG_ERROR("snprintf failed, will not apply cpu core parking!\n");
return 0;
}
*pos += ret;
}
if (first == last)
ret = snprintf(cpulist + *pos, ARG_MAX - (size_t)*pos, "%ld", first);
else
ret = snprintf(cpulist + *pos, ARG_MAX - (size_t)*pos, "%ld-%ld", first, last);
if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) {
LOG_ERROR("snprintf failed, will not apply cpu core parking!\n");
return 0;
}
*pos += ret;
return 1;
}
int game_mode_park_cpu(const GameModeCPUInfo *info)
{
if (!info || info->park_or_pin == IS_CPU_PIN)
return 0;
long first = -1, last = -1;
char cpulist[ARG_MAX];
int pos = 0;
for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) {
if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) &&
!CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) {
if (first == -1) {
first = cpu;
last = cpu;
} else if (last + 1 == cpu) {
last = cpu;
} else {
if (!log_state(cpulist, &pos, first, last))
return 0;
first = cpu;
last = cpu;
}
}
}
if (first != -1)
log_state(cpulist, &pos, first, last);
const char *const exec_args[] = {
"pkexec", LIBEXECDIR "/cpucorectl", "offline", cpulist, NULL,
};
LOG_MSG("Requesting parking of cores %s\n", cpulist);
int ret = run_external_process(exec_args, NULL, -1);
if (ret != 0) {
LOG_ERROR("Failed to park cpu cores\n");
return ret;
}
return 0;
}
int game_mode_unpark_cpu(const GameModeCPUInfo *info)
{
if (!info || info->park_or_pin == IS_CPU_PIN)
return 0;
long first = -1, last = -1;
char cpulist[ARG_MAX];
int pos = 0;
for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) {
if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) &&
!CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) {
if (first == -1) {
first = cpu;
last = cpu;
} else if (last + 1 == cpu) {
last = cpu;
} else {
if (!log_state(cpulist, &pos, first, last))
return 0;
first = cpu;
last = cpu;
}
}
}
if (first != -1)
log_state(cpulist, &pos, first, last);
const char *const exec_args[] = {
"pkexec", LIBEXECDIR "/cpucorectl", "online", cpulist, NULL,
};
LOG_MSG("Requesting unparking of cores %s\n", cpulist);
int ret = run_external_process(exec_args, NULL, -1);
if (ret != 0) {
LOG_ERROR("Failed to unpark cpu cores\n");
return ret;
}
return 0;
}
static void apply_affinity_mask(pid_t pid, size_t cpusetsize, const cpu_set_t *mask,
const bool be_silent)
{
char buffer[PATH_MAX];
char *proc_path = NULL;
DIR *proc_dir = NULL;
if (!(proc_path = buffered_snprintf(buffer, "/proc/%d/task", pid))) {
if (!be_silent) {
LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno));
}
return;
}
if (!(proc_dir = opendir(proc_path))) {
if (!be_silent) {
LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno));
}
return;
}
struct dirent *entry;
while ((entry = readdir(proc_dir))) {
if (entry->d_name[0] == '.')
continue;
int tid = atoi(entry->d_name);
if (sched_setaffinity(tid, cpusetsize, mask) != 0 && !be_silent)
LOG_ERROR("Failed to pin thread %d: %s\n", tid, strerror(errno));
}
closedir(proc_dir);
}
void game_mode_apply_core_pinning(const GameModeCPUInfo *info, const pid_t client,
const bool be_silent)
{
if (!info || info->park_or_pin == IS_CPU_PARK)
return;
if (!be_silent)
LOG_MSG("Pinning process...\n");
apply_affinity_mask(client, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep, be_silent);
}
void game_mode_undo_core_pinning(const GameModeCPUInfo *info, const pid_t client)
{
if (!info || info->park_or_pin == IS_CPU_PARK)
return;
LOG_MSG("Pinning process back to all online cores...\n");
apply_affinity_mask(client, CPU_ALLOC_SIZE(info->num_cpu), info->online, false);
}
void game_mode_free_cpu(GameModeCPUInfo **info)
{
if ((*info)) {
CPU_FREE((*info)->online);
(*info)->online = NULL;
CPU_FREE((*info)->to_keep);
(*info)->to_keep = NULL;
free(*info);
*info = NULL;
}
}

813
daemon/gamemode-dbus.c Normal file
View File

@ -0,0 +1,813 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
#define _GNU_SOURCE
#include "gamemode.h"
#include "common-helpers.h"
#include "common-logging.h"
#include "common-pidfds.h"
#ifdef USE_ELOGIND
#include <elogind/sd-bus.h>
#include <elogind/sd-daemon.h>
#else
#include <systemd/sd-bus.h>
#include <systemd/sd-daemon.h>
#endif
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define GAME_PATH_PREFIX "/com/feralinteractive/GameMode/Games"
/* maximum length of a valid game object path string:
* The path prefix including \0 (sizeof), another '/', and 10 digits for uint32_t ('%u')*/
#define GAME_PATH_MAX (sizeof(GAME_PATH_PREFIX) + 11)
/* systemd dbus components */
static sd_bus *bus = NULL;
static sd_bus_slot *slot = NULL;
/**
* Clean up our private dbus state
*/
static void clean_up(void)
{
if (slot) {
sd_bus_slot_unref(slot);
}
slot = NULL;
if (bus) {
sd_bus_unref(bus);
}
bus = NULL;
}
/**
* Handles the RegisterGame D-BUS Method
*/
static int method_register_game(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int pid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "i", &pid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_register(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the UnregisterGame D-BUS Method
*/
static int method_unregister_game(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int pid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "i", &pid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_unregister(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the QueryStatus D-BUS Method
*/
static int method_query_status(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int pid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "i", &pid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_query_status(context, (pid_t)pid, (pid_t)pid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the RegisterGameByPID D-BUS Method
*/
static int method_register_game_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = game_mode_context_register(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the UnregisterGameByPID D-BUS Method
*/
static int method_unregister_game_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = game_mode_context_unregister(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the QueryStatusByPID D-BUS Method
*/
static int method_query_status_by_pid(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int callerpid = 0;
int gamepid = 0;
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "ii", &callerpid, &gamepid);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int status = game_mode_context_query_status(context, (pid_t)gamepid, (pid_t)callerpid);
return sd_bus_reply_method_return(m, "i", status);
}
/**
* Handles the RegisterGameByPIDFd D-BUS Method
*/
static int method_register_game_by_pidfd(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int fds[2] = { -1, -1 };
pid_t pids[2] = { 0, 0 };
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "hh", &fds[0], &fds[1]);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = pidfds_to_pids(fds, pids, 2);
if (reply == 2)
reply = game_mode_context_register(context, pids[0], pids[1]);
else
reply = -1;
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the UnregisterGameByPIDFd D-BUS Method
*/
static int method_unregister_game_by_pidfd(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int fds[2] = { -1, -1 };
pid_t pids[2] = { 0, 0 };
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "hh", &fds[0], &fds[1]);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = pidfds_to_pids(fds, pids, 2);
if (reply == 2)
reply = game_mode_context_unregister(context, pids[0], pids[1]);
else
reply = -1;
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the QueryStatusByPIDFd D-BUS Method
*/
static int method_query_status_by_pidfd(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
int fds[2] = { -1, -1 };
pid_t pids[2] = { 0, 0 };
GameModeContext *context = userdata;
int ret = sd_bus_message_read(m, "hh", &fds[0], &fds[1]);
if (ret < 0) {
LOG_ERROR("Failed to parse input parameters: %s\n", strerror(-ret));
return ret;
}
int reply = pidfds_to_pids(fds, pids, 2);
if (reply == 2)
reply = game_mode_context_query_status(context, pids[0], pids[1]);
else
reply = -1;
return sd_bus_reply_method_return(m, "i", reply);
}
/**
* Handles the ClientCount D-BUS Property
*/
static int property_get_client_count(sd_bus *local_bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
GameModeContext *context = userdata;
int count;
count = game_mode_context_num_clients(context);
return sd_bus_message_append_basic(reply, 'i', &count);
}
/**
* Handles the Refresh Config request
*/
static int method_refresh_config(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
GameModeContext *context = userdata;
int status = game_mode_reload_config(context);
return sd_bus_reply_method_return(m, "i", status);
}
static inline void game_object_bus_path(pid_t pid, char path[static GAME_PATH_MAX])
{
snprintf(path, GAME_PATH_MAX, GAME_PATH_PREFIX "/%u", (uint32_t)pid);
}
/**
* Handles the List Games
*/
static int method_list_games(sd_bus_message *m, void *userdata,
__attribute__((unused)) sd_bus_error *ret_error)
{
GameModeContext *context = userdata;
sd_bus_message *reply = NULL;
unsigned int count;
pid_t *clients;
int r;
r = sd_bus_message_new_method_return(m, &reply);
if (r < 0)
return r;
r = sd_bus_message_open_container(reply, 'a', "(io)");
if (r < 0)
return r;
clients = game_mode_context_list_clients(context, &count);
for (unsigned int i = 0; i < count; i++) {
char path[GAME_PATH_MAX] = {
0,
};
pid_t pid = clients[i];
game_object_bus_path(pid, path);
r = sd_bus_message_append(reply, "(io)", (int32_t)pid, path);
if (r < 0)
break;
}
free(clients);
if (r < 0)
return r;
r = sd_bus_message_close_container(reply);
if (r < 0)
return r;
return sd_bus_send(NULL, reply, NULL);
}
/* Signal emission helper */
static void game_mode_client_send_game_signal(pid_t pid, bool new_game)
{
char path[GAME_PATH_MAX] = {
0,
};
int ret;
game_object_bus_path(pid, path);
ret = sd_bus_emit_signal(bus,
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
new_game ? "GameRegistered" : "GameUnregistered",
"io",
(int32_t)pid,
path);
if (ret < 0)
fprintf(stderr, "failed to emit signal: %s", strerror(-ret));
(void)sd_bus_emit_properties_changed(bus,
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
"ClientCount",
NULL);
}
/* Emit GameRegistered signal */
void game_mode_client_registered(pid_t pid)
{
game_mode_client_send_game_signal(pid, true);
}
/* Emit GameUnregistered signal */
void game_mode_client_unregistered(pid_t pid)
{
game_mode_client_send_game_signal(pid, false);
}
/**
* D-BUS vtable to dispatch virtual methods
*/
/* This bit seems to be formatted differently by different clang-format versions */
/* clang-format off */
static const sd_bus_vtable gamemode_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("ClientCount", "i", property_get_client_count, 0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_METHOD("RegisterGame", "i", "i", method_register_game, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGame", "i", "i", method_unregister_game, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatus", "i", "i", method_query_status, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RegisterGameByPID", "ii", "i", method_register_game_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGameByPID", "ii", "i", method_unregister_game_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatusByPID", "ii", "i", method_query_status_by_pid,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RegisterGameByPIDFd", "hh", "i", method_register_game_by_pidfd,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("UnregisterGameByPIDFd", "hh", "i", method_unregister_game_by_pidfd,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("QueryStatusByPIDFd", "hh", "i", method_query_status_by_pidfd,
SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("RefreshConfig", "", "i", method_refresh_config, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("ListGames", "", "a(io)", method_list_games, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("GameRegistered", "io", 0),
SD_BUS_SIGNAL("GameUnregistered", "io", 0),
SD_BUS_VTABLE_END
};
/**
* Game Objects
*/
static inline void pid_to_pointer(pid_t pid, void **pointer)
{
_Static_assert(sizeof (void *) >= sizeof (pid_t),
"pointer type not large enough to store pid_t");
*pointer = (void *) (intptr_t) pid;
}
static inline pid_t pid_from_pointer(const void *pointer)
{
return (pid_t) (intptr_t) pointer;
}
static int game_object_find(sd_bus *local_bus, const char *path, const char *interface,
void *userdata, void **found, sd_bus_error *ret_error)
{
static const char prefix[] = GAME_PATH_PREFIX "/";
const char *start;
unsigned long int n;
char *end;
if (strncmp(path, prefix, strlen(prefix)) != 0)
return 0;
start = path + strlen(prefix);
errno = 0;
n = strtoul(start, &end, 10);
if (start == end || errno != 0)
return 0;
pid_to_pointer((pid_t) n, found);
return 1;
}
static int game_node_enumerator(sd_bus *local_bus, const char *path, void *userdata,
char ***nodes,
__attribute__((unused)) sd_bus_error *ret_error)
{
GameModeContext *context = userdata;
unsigned int count;
pid_t *clients;
char **strv = NULL;
clients = game_mode_context_list_clients(context, &count);
strv = malloc (sizeof (char *) * (count + 1));
for (unsigned int i = 0; i < count; i++) {
char bus_path[GAME_PATH_MAX] = {0, };
game_object_bus_path(clients[i], bus_path);
strv[i] = strdup (bus_path);
}
strv[count] = NULL;
*nodes = strv;
free(clients);
return 1;
}
/**
* Handles the ProcessId property for Game objects
*/
static int game_object_get_process_id(sd_bus *local_bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata,
sd_bus_error *ret_error)
{
GameModeClient *client;
GameModeContext *context;
pid_t pid;
int pv;
int ret;
pid = pid_from_pointer(userdata);
context = game_mode_context_instance();
client = game_mode_context_lookup_client(context, pid);
pv = (int) pid;
if (client == NULL) {
return sd_bus_error_setf(ret_error,
SD_BUS_ERROR_UNKNOWN_OBJECT,
"No client registered with id '%d'", pv);
}
ret = sd_bus_message_append_basic(reply, 'i', &pv);
game_mode_client_unref(client);
return ret;
}
/**
* Handles the Exectuable property for Game objects
*/
static int game_object_get_executable(sd_bus *local_bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata,
sd_bus_error *ret_error)
{
GameModeClient *client;
GameModeContext *context;
const char *exec;
pid_t pid;
int ret;
pid = pid_from_pointer(userdata);
context = game_mode_context_instance();
client = game_mode_context_lookup_client(context, pid);
if (client == NULL) {
return sd_bus_error_setf(ret_error,
SD_BUS_ERROR_UNKNOWN_OBJECT,
"No client registered with id '%d'", (int) pid);
}
exec = game_mode_client_get_executable(client);
ret = sd_bus_message_append_basic(reply, 's', exec);
game_mode_client_unref(client);
return ret;
}
/**
* Handles the Requester property for Game objects
*/
static int game_object_get_requester(sd_bus *local_bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata,
sd_bus_error *ret_error)
{
GameModeClient *client;
GameModeContext *context;
pid_t requester;
pid_t pid;
int ret;
int pv;
pid = pid_from_pointer(userdata);
context = game_mode_context_instance();
client = game_mode_context_lookup_client(context, pid);
if (client == NULL) {
return sd_bus_error_setf(ret_error,
SD_BUS_ERROR_UNKNOWN_OBJECT,
"No client registered with id '%d'", (int) pid);
}
requester = game_mode_client_get_requester(client);
pv = (int) requester;
ret = sd_bus_message_append_basic(reply, 'i', &pv);
game_mode_client_unref(client);
return ret;
}
/**
* Handles the Timestamp property for Game objects
*/
static int game_object_get_timestamp(sd_bus *local_bus, const char *path, const char *interface,
const char *property, sd_bus_message *reply, void *userdata,
sd_bus_error *ret_error)
{
GameModeClient *client;
GameModeContext *context;
uint64_t timestamp;
pid_t pid;
int ret;
pid = pid_from_pointer(userdata);
context = game_mode_context_instance();
client = game_mode_context_lookup_client(context, pid);
if (client == NULL) {
return sd_bus_error_setf(ret_error,
SD_BUS_ERROR_UNKNOWN_OBJECT,
"No client registered with id '%d'", (int) pid);
}
timestamp = game_mode_client_get_timestamp(client);
ret = sd_bus_message_append_basic(reply, 't', &timestamp);
game_mode_client_unref(client);
return ret;
}
/* Same as above: this bit seems to be formatted differently by different clang-format versions */
/* clang-format off */
static const sd_bus_vtable game_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("ProcessId", "i", game_object_get_process_id, 0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Executable", "s", game_object_get_executable, 0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Requester", "i", game_object_get_requester, 0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Timestamp", "t", game_object_get_timestamp, 0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_VTABLE_END
};
/* clang-format on */
/**
* Main process loop for the daemon. Run until quitting has been requested.
*/
void game_mode_context_loop(GameModeContext *context)
{
/* Set up function to handle clean up of resources */
atexit(clean_up);
int ret = 0;
/* Connect to the session bus */
ret = sd_bus_open_user(&bus);
if (ret < 0) {
FATAL_ERROR("Failed to connect to the bus: %s\n", strerror(-ret));
}
/* Create the object to allow connections */
ret = sd_bus_add_object_vtable(bus,
&slot,
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
gamemode_vtable,
context);
if (ret < 0) {
FATAL_ERROR("Failed to install GameMode object: %s\n", strerror(-ret));
}
ret = sd_bus_add_fallback_vtable(bus,
&slot,
GAME_PATH_PREFIX,
"com.feralinteractive.GameMode.Game",
game_vtable,
game_object_find,
context);
if (ret < 0) {
FATAL_ERROR("Failed to install Game object: %s\n", strerror(-ret));
}
ret = sd_bus_add_node_enumerator(bus, &slot, GAME_PATH_PREFIX, game_node_enumerator, context);
if (ret < 0) {
FATAL_ERROR("Failed to install Game object enumerator: %s\n", strerror(-ret));
}
/* Request our name */
ret = sd_bus_request_name(bus, "com.feralinteractive.GameMode", 0);
if (ret < 0) {
FATAL_ERROR("Failed to acquire service name: %s\n", strerror(-ret));
}
LOG_MSG("Successfully initialised bus with name [%s]...\n", "com.feralinteractive.GameMode");
sd_notifyf(0, "STATUS=%sGameMode is ready to be activated.%s\n", "\x1B[1;36m", "\x1B[0m");
/* Now loop, waiting for callbacks */
for (;;) {
ret = sd_bus_process(bus, NULL);
if (ret < 0) {
FATAL_ERROR("Failure when processing the bus: %s\n", strerror(-ret));
}
/* We're done processing */
if (ret > 0) {
continue;
}
/* Wait for more */
ret = sd_bus_wait(bus, (uint64_t)-1);
if (ret < 0 && -ret != EINTR) {
FATAL_ERROR("Failure when waiting on bus: %s\n", strerror(-ret));
}
}
}
struct GameModeIdleInhibitor {
sd_bus *bus;
unsigned int cookie;
};
/**
* Attempts to inhibit the screensaver
* Uses the "org.freedesktop.ScreenSaver" interface
*/
GameModeIdleInhibitor *game_mode_create_idle_inhibitor(void)
{
sd_bus_message *msg = NULL;
sd_bus *bus_local = NULL;
sd_bus_error err = SD_BUS_ERROR_NULL;
// Open the user bus
int ret = sd_bus_open_user(&bus_local);
if (ret < 0) {
LOG_ERROR("Could not connect to user bus: %s\n", strerror(-ret));
return NULL;
}
ret = sd_bus_call_method(bus_local,
"org.freedesktop.ScreenSaver",
"/org/freedesktop/ScreenSaver",
"org.freedesktop.ScreenSaver",
"Inhibit",
&err,
&msg,
"ss",
"com.feralinteractive.GameMode",
"GameMode Activated");
if (ret < 0) {
LOG_ERROR(
"Failed to call Inhibit on org.freedesktop.ScreenSaver: %s\n"
"\t%s\n"
"\t%s\n",
strerror(-ret),
err.name,
err.message);
sd_bus_close(bus_local);
sd_bus_unrefp(&bus_local);
return NULL;
}
// Read the reply
unsigned int cookie = 0;
ret = sd_bus_message_read(msg, "u", &cookie);
if (ret < 0) {
LOG_ERROR("Invalid response from Inhibit on org.freedesktop.ScreenSaver: %s\n",
strerror(-ret));
sd_bus_close(bus_local);
sd_bus_unrefp(&bus_local);
return NULL;
}
GameModeIdleInhibitor *inhibitor = malloc(sizeof(GameModeIdleInhibitor));
if (inhibitor == NULL) {
sd_bus_close(bus_local);
sd_bus_unrefp(&bus_local);
return NULL;
}
inhibitor->bus = bus_local;
inhibitor->cookie = cookie;
return inhibitor;
}
void game_mode_destroy_idle_inhibitor(GameModeIdleInhibitor *inhibitor)
{
sd_bus_message *msg = NULL;
sd_bus_error err = SD_BUS_ERROR_NULL;
if (inhibitor == NULL) {
return;
}
int ret = sd_bus_call_method(inhibitor->bus,
"org.freedesktop.ScreenSaver",
"/org/freedesktop/ScreenSaver",
"org.freedesktop.ScreenSaver",
"UnInhibit",
&err,
&msg,
"u",
inhibitor->cookie);
if (ret < 0) {
LOG_ERROR(
"Failed to call UnInhibit on org.freedesktop.ScreenSaver: %s\n"
"\t%s\n"
"\t%s\n",
strerror(-ret),
err.name,
err.message);
}
sd_bus_close(inhibitor->bus);
sd_bus_unrefp(&inhibitor->bus);
free(inhibitor);
}

View File

@ -1,96 +0,0 @@
/*
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.
*/
#define _GNU_SOURCE
#include "gamemode.h"
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/**
* Lookup the process environment for a specific variable or return NULL.
* Requires an open directory FD from /proc/PID.
*/
char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var)
{
char *environ = NULL;
int fd = openat(proc_fd, "environ", O_RDONLY | O_CLOEXEC);
if (fd != -1) {
FILE *stream = fdopen(fd, "r");
if (stream) {
/* Read every \0 terminated line from the environment */
char *line = NULL;
size_t len = 0;
size_t pos = strlen(var) + 1;
while (!environ && (getdelim(&line, &len, 0, stream) != -1)) {
/* Find a match including the "=" suffix */
if ((len > pos) && (strncmp(line, var, strlen(var)) == 0) && (line[pos - 1] == '='))
environ = strndup(line + pos, len - pos);
}
free(line);
fclose(stream);
} else
close(fd);
}
/* If found variable is empty, skip it */
if (environ && !strlen(environ)) {
free(environ);
environ = NULL;
}
return environ;
}
/**
* Lookup the home directory of the user in a safe way.
*/
char *game_mode_lookup_user_home(void)
{
/* Try loading env HOME first */
const char *home = secure_getenv("HOME");
if (!home) {
/* If HOME is not defined (or out of context), fall back to passwd */
struct passwd *pw = getpwuid(getuid());
if (!pw)
return NULL;
home = pw->pw_dir;
}
/* Try to allocate into our heap */
return home ? strdup(home) : NULL;
}

View File

@ -1,7 +1,7 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -32,15 +32,17 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "config.h"
#include "external-helper.h"
#include "helpers.h"
#include "logging.h"
#include "common-external.h"
#include "common-gpu.h"
#include "common-helpers.h"
#include "common-logging.h"
#include "gamemode.h"
#include "gamemode-config.h"
#include "daemon_config.h"
#include "gpu-control.h"
#include "build-config.h"
_Static_assert(CONFIG_VALUE_MAX == GPU_VALUE_MAX, "Config max value and GPU value out of sync!");
/**
* Attempts to identify the current in use GPU information
@ -84,6 +86,7 @@ int game_mode_initialise_gpu(GameModeConfig *config, GameModeGPUInfo **info)
new_info->vendor = gamemode_get_gpu_vendor(new_info->device);
if (!GPUVendorValid(new_info->vendor)) {
LOG_ERROR("Found invalid vendor, will not apply optimisations!\n");
free(new_info);
return -1;
}
@ -140,11 +143,6 @@ void game_mode_free_gpu(GameModeGPUInfo **info)
*info = NULL;
}
//#include <linux/limits.h>
//#include <stdio.h>
//#include <sys/wait.h>
//#include <unistd.h>
/**
* Applies GPU optimisations when gamemode is active and removes them after
*/
@ -169,7 +167,7 @@ int game_mode_apply_gpu(const GameModeGPUInfo *info)
// Set up our command line to pass to gpuclockctl
const char *const exec_args[] = {
"/usr/bin/pkexec",
"pkexec",
LIBEXECDIR "/gpuclockctl",
device,
"set",
@ -224,7 +222,8 @@ int game_mode_get_gpu(GameModeGPUInfo *info)
}
break;
case Vendor_AMD:
strncpy(info->amd_performance_level, buffer, CONFIG_VALUE_MAX);
strncpy(info->amd_performance_level, buffer, sizeof(info->amd_performance_level) - 1);
info->amd_performance_level[sizeof(info->amd_performance_level) - 1] = '\0';
break;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,14 +31,13 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "daemon_config.h"
#include "gamemode.h"
#include "helpers.h"
#include "logging.h"
#include "common-helpers.h"
#include "common-logging.h"
#include "gamemode-config.h"
#include <errno.h>
#include <dirent.h>
#include <sys/syscall.h>
#include <unistd.h>
/**
* Define the syscall interface in Linux because it is missing from glibc
@ -61,7 +60,7 @@ POSSIBILITY OF SUCH DAMAGE.
#endif
#ifndef IOPRIO_PRIO_DATA
#define IOPRIO_PRIO_DATA(mask) ((mask)&IOPRIO_PRIO_MASK)
#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK)
#endif
#ifndef IOPRIO_PRIO_VALUE
@ -86,6 +85,25 @@ static inline int ioprio_set(int which, int who, int ioprio)
return (int)syscall(SYS_ioprio_set, which, who, ioprio);
}
static inline int ioprio_get(int which, int who)
{
return (int)syscall(SYS_ioprio_get, which, who);
}
/**
* Get the i/o priorities
*/
int game_mode_get_ioprio(const pid_t client)
{
int ret = ioprio_get(IOPRIO_WHO_PROCESS, client);
if (ret == -1) {
LOG_ERROR("Failed to get ioprio value for [%d] with error %s\n", client, strerror(errno));
ret = IOPRIO_DONT_SET;
}
/* We support only IOPRIO_CLASS_BE as IOPRIO_CLASS_RT required CAP_SYS_ADMIN */
return IOPRIO_PRIO_DATA(ret);
}
/**
* Apply io priorities
*
@ -93,49 +111,80 @@ static inline int ioprio_set(int which, int who, int ioprio)
* and can possibly reduce lags or latency when a game has to load assets
* on demand.
*/
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client)
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client, int expected)
{
if (expected == IOPRIO_DONT_SET)
/* Silently bail if fed a don't set (invalid) */
return;
GameModeConfig *config = game_mode_config_from_context(self);
LOG_MSG("Setting scheduling policies...\n");
/*
* read configuration "ioprio" (0..7)
*/
/* read configuration "ioprio" (0..7) */
int ioprio = (int)config_get_ioprio_value(config);
if (IOPRIO_RESET_DEFAULT == ioprio) {
LOG_MSG("IO priority will be reset to default behavior (based on CPU priority).\n");
ioprio = 0;
} else if (IOPRIO_DONT_SET == ioprio) {
/* Special value to simply not set the value */
if (ioprio == IOPRIO_DONT_SET)
return;
} else {
/* maybe clamp the value */
int invalid_ioprio = ioprio;
ioprio = CLAMP(0, 7, ioprio);
if (ioprio != invalid_ioprio)
LOG_ONCE(ERROR,
"IO priority value %d invalid, clamping to %d\n",
invalid_ioprio,
ioprio);
/* We support only IOPRIO_CLASS_BE as IOPRIO_CLASS_RT required CAP_SYS_ADMIN */
ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, ioprio);
LOG_MSG("Setting ioprio value...\n");
/* If fed the default, we'll try and reset the value back */
if (expected != IOPRIO_DEFAULT) {
expected = (int)ioprio;
ioprio = IOPRIO_DEFAULT;
}
/*
* Actually apply the io priority
*/
int c = IOPRIO_PRIO_CLASS(ioprio), p = IOPRIO_PRIO_DATA(ioprio);
if (ioprio_set(IOPRIO_WHO_PROCESS, client, ioprio) == 0) {
if (0 == ioprio)
LOG_MSG("Resetting client [%d] IO priority.\n", client);
else
LOG_MSG("Setting client [%d] IO priority to (%d,%d).\n", client, c, p);
} else {
LOG_ERROR("Setting client [%d] IO priority to (%d,%d) failed with error %d, ignoring\n",
client,
c,
p,
errno);
/* Open the tasks dir for the client */
char tasks[128];
snprintf(tasks, sizeof(tasks), "/proc/%d/task", client);
DIR *client_task_dir = opendir(tasks);
if (client_task_dir == NULL) {
LOG_ERROR("Could not inspect tasks for client [%d]! Skipping ioprio optimisation.\n",
client);
return;
}
/* Iterate for all tasks of client process */
struct dirent *tid_entry;
while ((tid_entry = readdir(client_task_dir)) != NULL) {
/* Skip . and .. */
if (tid_entry->d_name[0] == '.')
continue;
/* task name is the name of the file */
int tid = atoi(tid_entry->d_name);
int current = game_mode_get_ioprio(tid);
if (current == IOPRIO_DONT_SET) {
/* Couldn't get the ioprio value
* This could simply mean that the thread exited before fetching the ioprio
* So we should continue
*/
} else if (current != expected) {
/* Don't try and adjust the ioprio value if the value we got doesn't match default */
LOG_ERROR("Skipping ioprio on client [%d,%d]: ioprio was (%d) but we expected (%d)\n",
client,
tid,
current,
expected);
} else {
/*
* For now we only support IOPRIO_CLASS_BE
* IOPRIO_CLASS_RT requires CAP_SYS_ADMIN but should be possible with a polkit process
*/
int p = ioprio;
ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, ioprio);
if (ioprio_set(IOPRIO_WHO_PROCESS, tid, ioprio) != 0) {
/* This could simply mean the thread is gone now, as above */
LOG_ERROR(
"Setting client [%d,%d] IO priority to (%d) failed with error %d, ignoring.\n",
client,
tid,
p,
errno);
}
}
}
closedir(client_task_dir);
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,13 +31,12 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "daemon_config.h"
#include "gamemode.h"
#include "logging.h"
#include "common-logging.h"
#include "gamemode-config.h"
#include <errno.h>
#include <dirent.h>
#include <sched.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/sysinfo.h>
@ -54,13 +53,28 @@ POSSIBILITY OF SUCH DAMAGE.
* This tries to change the scheduler of the client to soft realtime mode
* available in some kernels as SCHED_ISO. It also tries to adjust the nice
* level. If some of each fail, ignore this and log a warning.
*
* We don't need to store the current values because when the client exits,
* everything will be good: Scheduling is only applied to the client and
* its children.
*/
void game_mode_apply_renice(const GameModeContext *self, const pid_t client)
#define RENICE_INVALID -128 /* Special value to store invalid value */
int game_mode_get_renice(const pid_t client)
{
/* Clear errno as -1 is a regitimate return */
errno = 0;
int priority = getpriority(PRIO_PROCESS, (id_t)client);
if (priority == -1 && errno) {
LOG_ERROR("getprority(PRIO_PROCESS, %d) failed : %s\n", client, strerror(errno));
return RENICE_INVALID;
}
return -priority;
}
/* If expected is 0 then we try to apply our renice, otherwise, we try to remove it */
void game_mode_apply_renice(const GameModeContext *self, const pid_t client, int expected)
{
if (expected == RENICE_INVALID)
/* Silently bail if fed an invalid value */
return;
GameModeConfig *config = game_mode_config_from_context(self);
/*
@ -69,26 +83,70 @@ void game_mode_apply_renice(const GameModeContext *self, const pid_t client)
long int renice = config_get_renice_value(config);
if (renice == 0) {
return;
} else if ((renice < 1) || (renice > 20)) {
LOG_ONCE(ERROR, "Configured renice value '%ld' is invalid, will not renice.\n", renice);
return;
} else {
renice = -renice;
}
/*
* don't adjust priority if it was already adjusted
*/
if (getpriority(PRIO_PROCESS, (id_t)client) != 0) {
LOG_ERROR("Refused to renice client [%d]: already reniced\n", client);
} else if (setpriority(PRIO_PROCESS, (id_t)client, (int)renice)) {
LOG_HINTED(ERROR,
"Failed to renice client [%d], ignoring error condition: %s\n",
" -- Your user may not have permission to do this. Please read the docs\n"
" -- to learn how to adjust the pam limits.\n",
client,
strerror(errno));
/* Invert the renice value */
renice = -renice;
/* When expected is non-zero, we should try and remove the renice only if it doesn't match the
* expected value */
if (expected != 0) {
expected = (int)renice;
renice = 0;
}
/* Open the tasks dir for the client */
char tasks[128];
snprintf(tasks, sizeof(tasks), "/proc/%d/task", client);
DIR *client_task_dir = opendir(tasks);
if (client_task_dir == NULL) {
LOG_ERROR("Could not inspect tasks for client [%d]! Skipping ioprio optimisation.\n",
client);
return;
}
/* Iterate for all tasks of client process */
struct dirent *tid_entry;
while ((tid_entry = readdir(client_task_dir)) != NULL) {
/* Skip . and .. */
if (tid_entry->d_name[0] == '.')
continue;
/* task name is the name of the file */
int tid = atoi(tid_entry->d_name);
/* Clear errno as -1 is a regitimate return */
errno = 0;
int prio = getpriority(PRIO_PROCESS, (id_t)tid);
if (prio == -1 && errno) {
/* Process may well have ended */
LOG_ERROR("getpriority failed for client [%d,%d] with error: %s\n",
client,
tid,
strerror(errno));
} else if (prio != expected) {
/*
* Don't adjust priority if it does not match the expected value
* ie. Another process has changed it, or it began non-standard
*/
LOG_ERROR("Refused to renice client [%d,%d]: prio was (%d) but we expected (%d)\n",
client,
tid,
prio,
expected);
} else if (setpriority(PRIO_PROCESS, (id_t)tid, (int)renice)) {
LOG_HINTED(ERROR,
"Failed to renice client [%d,%d], ignoring error condition: %s\n",
" -- Your user may not have permission to do this. Please read the docs\n"
" -- to learn how to adjust the pam limits.\n",
client,
tid,
strerror(errno));
}
}
closedir(client_task_dir);
}
void game_mode_apply_scheduling(const GameModeContext *self, const pid_t client)

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,20 +31,22 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "common-external.h"
#include "common-governors.h"
#include "common-gpu.h"
#include "common-helpers.h"
#include "common-logging.h"
#include "common-profile.h"
#include "gamemode.h"
#include "helpers.h"
#include "logging.h"
#include <libgen.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "daemon_config.h"
#include "external-helper.h"
#include "gamemode-config.h"
#include "gamemode_client.h"
#include "governors-query.h"
#include "gpu-control.h"
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/wait.h>
struct GameModeConfig;
/* Initial verify step to ensure gamemode isn't already active */
static int verify_gamemode_initial(struct GameModeConfig *config)
@ -161,7 +163,7 @@ static int run_basic_client_tests(void)
return -1;
}
/* Verify that gamemode is now innactive */
/* Verify that gamemode is now inactive */
if (verify_deactivated() != 0)
return -1;
@ -244,7 +246,7 @@ static int run_dual_client_tests(void)
usleep(100000);
}
/* Verify that gamemode is now innactive */
/* Verify that gamemode is now inactive */
if (verify_deactivated() != 0)
return -1;
@ -267,14 +269,14 @@ static int run_gamemoderun_and_reaper_tests(struct GameModeConfig *config)
/* Close stdout, we don't care if sh prints anything */
fclose(stdout);
/* Preload into sh and then kill it */
if (execl("/usr/bin/gamemoderun", "/usr/bin/gamemoderun", "sh", (char *)NULL) == -1) {
if (execlp("gamemoderun", "gamemoderun", "sleep", "5", (char *)NULL) == -1) {
LOG_ERROR("failed to launch gamemoderun with execl: %s\n", strerror(errno));
return -1;
}
}
/* Give the child a chance to reqeust gamemode */
usleep(10000);
usleep(100000);
/* Check that when we request gamemode, it replies that the other client is connected */
if (verify_other_client_connected() != 0)
@ -298,7 +300,7 @@ static int run_gamemoderun_and_reaper_tests(struct GameModeConfig *config)
LOG_MSG("...Waiting for reaper thread (reaper_frequency set to %ld seconds)...\n", freq);
sleep((unsigned int)freq);
/* Verify that gamemode is now innactive */
/* Verify that gamemode is now inactive */
if (verify_deactivated() != 0)
return -1;
@ -323,7 +325,7 @@ static int run_cpu_governor_tests(struct GameModeConfig *config)
if (defaultgov[0] == '\0') {
const char *currentgov = get_gov_state();
if (currentgov) {
strncpy(defaultgov, currentgov, CONFIG_VALUE_MAX);
strncpy(defaultgov, currentgov, CONFIG_VALUE_MAX - 1);
} else {
LOG_ERROR(
"Could not get current CPU governor state, this indicates an error! See rest "
@ -356,6 +358,62 @@ static int run_cpu_governor_tests(struct GameModeConfig *config)
return 0;
}
/* Check the platform profile setting works */
static int run_platform_profile_tests(struct GameModeConfig *config)
{
if (!profile_exists())
return 1;
/* get the two config parameters we care about */
char desiredprof[CONFIG_VALUE_MAX] = { 0 };
config_get_desired_profile(config, desiredprof);
if (desiredprof[0] == '\0')
strcpy(desiredprof, "performance");
char defaultprof[CONFIG_VALUE_MAX] = { 0 };
config_get_default_profile(config, defaultprof);
if (defaultprof[0] == '\0') {
const char *currentprof = get_profile_state();
if (currentprof) {
strncpy(defaultprof, currentprof, CONFIG_VALUE_MAX - 1);
} else {
LOG_ERROR(
"Could not get current platform profile state, this indicates an error! See rest "
"of log.\n");
return -1;
}
}
/* Start gamemode */
gamemode_request_start();
/* Verify the platform profile is the desired one */
const char *currentprof = get_profile_state();
if (strncmp(currentprof, desiredprof, CONFIG_VALUE_MAX) != 0) {
LOG_ERROR("Platform profile was not set to %s (was actually %s)!\n",
desiredprof,
currentprof);
gamemode_request_end();
return -1;
}
/* End gamemode */
gamemode_request_end();
/* Verify the platform profile has been set back */
currentprof = get_profile_state();
if (strncmp(currentprof, defaultprof, CONFIG_VALUE_MAX) != 0) {
LOG_ERROR("Platform profile was not set back to %s (was actually %s)!\n",
defaultprof,
currentprof);
return -1;
}
return 0;
}
static int run_custom_scripts_tests(struct GameModeConfig *config)
{
int scriptstatus = 0;
@ -368,7 +426,7 @@ static int run_custom_scripts_tests(struct GameModeConfig *config)
if (startscripts[0][0] != '\0') {
int i = 0;
while (*startscripts[i] != '\0' && i < CONFIG_LIST_MAX) {
while (i < CONFIG_LIST_MAX && *startscripts[i] != '\0') {
LOG_MSG(":::: Running start script [%s]\n", startscripts[i]);
const char *args[] = { "/bin/sh", "-c", startscripts[i], NULL };
@ -391,7 +449,7 @@ static int run_custom_scripts_tests(struct GameModeConfig *config)
if (endscripts[0][0] != '\0') {
int i = 0;
while (*endscripts[i] != '\0' && i < CONFIG_LIST_MAX) {
while (i < CONFIG_LIST_MAX && *endscripts[i] != '\0') {
LOG_MSG(":::: Running end script [%s]\n", endscripts[i]);
const char *args[] = { "/bin/sh", "-c", endscripts[i], NULL };
@ -446,7 +504,8 @@ int run_gpu_optimisation_tests(struct GameModeConfig *config)
long expected_mem = gpuinfo->nv_mem;
long expected_nv_powermizer_mode = gpuinfo->nv_powermizer_mode;
char expected_amd_performance_level[CONFIG_VALUE_MAX];
strncpy(expected_amd_performance_level, gpuinfo->amd_performance_level, CONFIG_VALUE_MAX);
strncpy(expected_amd_performance_level, gpuinfo->amd_performance_level, CONFIG_VALUE_MAX - 1);
expected_amd_performance_level[CONFIG_VALUE_MAX - 1] = '\0';
/* Get current stats */
game_mode_get_gpu(gpuinfo);
@ -454,7 +513,8 @@ int run_gpu_optimisation_tests(struct GameModeConfig *config)
long original_nv_mem = gpuinfo->nv_mem;
long original_nv_powermizer_mode = gpuinfo->nv_powermizer_mode;
char original_amd_performance_level[CONFIG_VALUE_MAX];
strncpy(original_amd_performance_level, gpuinfo->amd_performance_level, CONFIG_VALUE_MAX);
strncpy(original_amd_performance_level, gpuinfo->amd_performance_level, CONFIG_VALUE_MAX - 1);
original_amd_performance_level[CONFIG_VALUE_MAX - 1] = '\0';
/* account for when settings are not set */
if (expected_nv_powermizer_mode == -1)
@ -534,6 +594,249 @@ int run_gpu_optimisation_tests(struct GameModeConfig *config)
return gpustatus;
}
/**
* Multithreaded process simulation
*
* Some of the optimisations that gamemode implements needs to be tested against a full process
* tree, otherwise we may only be applying them to only the main thread
*/
typedef struct {
pthread_barrier_t *barrier;
pid_t this;
} ThreadInfo;
static void *fake_thread_wait(void *arg)
{
ThreadInfo *info = (ThreadInfo *)arg;
/* Store the thread ID */
info->this = (pid_t)syscall(SYS_gettid);
/**
* Wait twice
* First to sync that all threads have started
* Second to sync all threads exiting
*/
int ret = 0;
ret = pthread_barrier_wait(info->barrier);
if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
FATAL_ERROR("pthread_barrier_wait failed in child with error %d!\n", ret);
ret = pthread_barrier_wait(info->barrier);
if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
FATAL_ERROR("pthread_barrier_wait failed in child with error %d!\n", ret);
return NULL;
}
/* Runs a process tree in a child and tests each thread */
static pid_t run_tests_on_process_tree(int inactive, int active, int (*func)(pid_t))
{
/* Create a fake game-like multithreaded fork */
pid_t child = fork();
if (child == 0) {
/* Some stetup */
bool fail = false;
const unsigned int numthreads = 3;
pthread_barrier_t barrier;
pthread_barrier_init(&barrier, NULL, numthreads + 1);
/* First, request gamemode for this child process before it created the threads */
gamemode_request_start();
/* Spawn a few child threads */
pthread_t threads[numthreads];
ThreadInfo info[numthreads];
for (unsigned int i = 0; i < numthreads; i++) {
info[i].barrier = &barrier;
int err = pthread_create(&threads[i], NULL, fake_thread_wait, &info[i]);
if (err != 0) {
LOG_ERROR("Failed to spawn thread! Error: %d\n", err);
exit(EXIT_FAILURE);
}
}
/* Wait for threads to be created */
pthread_barrier_wait(&barrier);
/* Test each spawned thread */
for (unsigned int i = 0; i < numthreads; i++)
fail |= (active != func(info[i].this));
if (fail) {
LOG_ERROR("Initial values for new threads were incorrect!\n");
gamemode_request_end();
exit(-1);
}
/* Request gamemode end */
gamemode_request_end();
/* Test each spawned thread */
for (unsigned int i = 0; i < numthreads; i++)
fail |= (inactive != func(info[i].this));
if (fail) {
LOG_ERROR("values for threads were not reset after gamemode_request_end!\n");
exit(-1);
}
/* Request gamemode again - this time after threads were created */
gamemode_request_start();
/* Test each spawned thread */
for (unsigned int i = 0; i < numthreads; i++)
fail |= (active != func(info[i].this));
if (fail) {
LOG_ERROR("values for threads were not set correctly!\n");
gamemode_request_end();
exit(-1);
}
/* Request gamemode end */
gamemode_request_end();
/* Test each spawned thread */
for (unsigned int i = 0; i < numthreads; i++)
fail |= (inactive != func(info[i].this));
if (fail) {
LOG_ERROR("values for threads were not reset after gamemode_request_end!\n");
exit(-1);
}
/* Tell the threads to continue */
pthread_barrier_wait(&barrier);
/* Wait for threads to join */
int ret = 0;
for (unsigned int i = 0; i < numthreads; i++)
ret &= pthread_join(threads[i], NULL);
if (ret != 0)
LOG_ERROR("Thread cleanup in multithreaded tests failed!\n");
/* We're done, so return the error code generated */
exit(ret);
}
/* Wait for the child */
int wstatus = 0;
waitpid(child, &wstatus, 0);
int status = 0;
if (WIFEXITED(wstatus))
status = WEXITSTATUS(wstatus);
else {
LOG_ERROR("Multithreaded child exited abnormally!\n");
status = -1;
}
return status;
}
int run_renice_tests(struct GameModeConfig *config)
{
/* read configuration "renice" (1..20) */
long int renice = config_get_renice_value(config);
if (renice == 0) {
return 1; /* not configured */
}
/* Verify renice starts at 0 */
int val = game_mode_get_renice(getpid());
if (val != 0) {
LOG_ERROR("Initial renice value is non-zero: %d\n", val);
return -1;
}
int ret = 0;
/* Ask for gamemode for ourselves */
gamemode_request_start();
/* Check renice is now requested value */
val = game_mode_get_renice(getpid());
if (val != renice) {
LOG_ERROR(
"renice value not set correctly after gamemode_request_start\nExpected: %ld, Was: %d\n",
renice,
val);
ret = -1;
}
/* End gamemode for ourselves */
gamemode_request_end();
/* Check renice is returned to correct value */
val = game_mode_get_renice(getpid());
if (val != 0) {
LOG_ERROR("renice value non-zero after gamemode_request_end\nExpected: 0, Was: %d\n", val);
ret = -1;
}
/* Check multiprocess nice works as well */
val = run_tests_on_process_tree(0, (int)renice, game_mode_get_renice);
if (val != 0) {
LOG_ERROR("Multithreaded renice tests failed!\n");
ret = -1;
}
return ret;
}
int run_ioprio_tests(struct GameModeConfig *config)
{
/* read configuration "ioprio" */
long int ioprio = config_get_ioprio_value(config);
if (ioprio == IOPRIO_DONT_SET) {
return 1; /* not configured */
}
/* Verify ioprio starts at 0 */
int val = game_mode_get_ioprio(getpid());
if (val != IOPRIO_DEFAULT) {
LOG_ERROR("Initial ioprio value is non-default\nExpected: %d, Was: %d\n",
IOPRIO_DEFAULT,
val);
return -1;
}
int ret = 0;
/* Ask for gamemode for ourselves */
gamemode_request_start();
/* Check renice is now requested value */
val = game_mode_get_ioprio(getpid());
if (val != ioprio) {
LOG_ERROR(
"ioprio value not set correctly after gamemode_request_start\nExpected: %ld, Was: %d\n",
ioprio,
val);
ret = -1;
}
/* End gamemode for ourselves */
gamemode_request_end();
/* Check ioprio is returned to correct value */
val = game_mode_get_ioprio(getpid());
if (val != IOPRIO_DEFAULT) {
LOG_ERROR("ioprio value non-default after gamemode_request_end\nExpected: %d, Was: %d\n",
IOPRIO_DEFAULT,
val);
ret = -1;
}
/* Check multiprocess nice works as well */
val = run_tests_on_process_tree(IOPRIO_DEFAULT, (int)ioprio, game_mode_get_ioprio);
if (val != 0) {
LOG_ERROR("Multithreaded ioprio tests failed!\n");
ret = -1;
}
return ret;
}
/**
* game_mode_run_feature_tests runs a set of tests for each current feature (based on the current
* config) returns 0 for success, -1 for failure
@ -555,8 +858,29 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
LOG_MSG("::: Passed\n");
else {
LOG_MSG("::: Failed!\n");
LOG_MSG(" -- You may need to add your user to the gamemode group:");
LOG_MSG(" -- $ sudo usermod -aG gamemode $(whoami)");
// Consider the CPU governor feature required
status = 1;
status = -1;
}
}
/* Does the platform profile get set properly? */
{
LOG_MSG("::: Verifying platform profile setting\n");
int profstatus = run_platform_profile_tests(config);
if (profstatus == 1)
LOG_MSG("::: Passed (platform profile not supported)\n");
else if (profstatus == 0)
LOG_MSG("::: Passed\n");
else {
LOG_MSG("::: Failed!\n");
LOG_MSG(" -- You may need to add your user to the gamemode group:");
LOG_MSG(" -- $ sudo usermod -aG gamemode $(whoami)");
// If available, setting the platform profile should work
status = -1;
}
}
@ -572,7 +896,7 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
else {
LOG_MSG("::: Failed!\n");
// Any custom scripts should be expected to work
status = 1;
status = -1;
}
}
@ -588,18 +912,45 @@ static int game_mode_run_feature_tests(struct GameModeConfig *config)
else {
LOG_MSG("::: Failed!\n");
// Any custom scripts should be expected to work
status = 1;
status = -1;
}
}
/* Does the screensaver get inhibited? */
/* TODO: Unknown if this is testable, org.freedesktop.ScreenSaver has no query method */
/* Was the process reniced? */
/* Was the scheduling applied? */
/* Were io priorities changed? */
/* Note: These don't get cleared up on un-register, so will have already been applied */
{
LOG_MSG("::: Verifying renice\n");
int renicestatus = run_renice_tests(config);
if (renicestatus == 1)
LOG_MSG("::: Passed (no renice configured)\n");
else if (renicestatus == 0)
LOG_MSG("::: Passed\n");
else {
LOG_MSG("::: Failed!\n");
// Renice should be expected to work, if set
status = -1;
}
}
/* Was the process ioprio set? */
{
LOG_MSG("::: Verifying ioprio\n");
int iopriostatus = run_ioprio_tests(config);
if (iopriostatus == 1)
LOG_MSG("::: Passed (no ioprio configured)\n");
else if (iopriostatus == 0)
LOG_MSG("::: Passed\n");
else {
LOG_MSG("::: Failed!\n");
status = -1;
}
}
/* TODO */
/* Was the scheduling applied and removed? Does it get applied to a full process tree? */
/* Does the screensaver get inhibited? Unknown if this is testable, org.freedesktop.ScreenSaver
* has no query method */
if (status != -1)
LOG_MSG(":: Passed%s\n\n", status > 0 ? " (with optional failures)" : "");
@ -705,7 +1056,7 @@ static int run_supervisor_tests(void)
*
* returns 0 for success, -1 for failure
*/
int game_mode_run_client_tests()
int game_mode_run_client_tests(void)
{
int status = 0;
@ -725,7 +1076,6 @@ int game_mode_run_client_tests()
return -1;
/* Controls whether we require a supervisor to actually make requests */
/* TODO: This effects all tests below */
if (config_get_require_supervisor(config) != 0) {
LOG_ERROR("Tests currently unsupported when require_supervisor is set\n");
return -1;

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -32,18 +32,17 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "gamemode.h"
#include "helpers.h"
#include "logging.h"
#include "common-helpers.h"
#include "common-logging.h"
#include <ctype.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
/**
* Detect if the process is a wine preloader process
*/
bool game_mode_detect_wine_preloader(const char *exe)
static bool game_mode_detect_wine_preloader(const char *exe)
{
return (strtail(exe, "/wine-preloader") || strtail(exe, "/wine64-preloader"));
}
@ -51,20 +50,104 @@ bool game_mode_detect_wine_preloader(const char *exe)
/**
* Detect if the process is a wine loader process
*/
bool game_mode_detect_wine_loader(const char *exe)
static bool game_mode_detect_wine_loader(const char *exe)
{
return (strtail(exe, "/wine") || strtail(exe, "/wine64"));
}
/**
* Opens the process environment for a specific PID and returns
* a file descriptor to the directory /proc/PID. Doing it that way prevents
* the directory going MIA when a process exits while we are looking at it
* and allows us to handle fewer error cases.
*/
static procfd_t game_mode_open_proc(const pid_t pid)
{
char buffer[PATH_MAX];
const char *proc_path = buffered_snprintf(buffer, "/proc/%d", pid);
return proc_path ? open(proc_path, O_RDONLY | O_CLOEXEC) : INVALID_PROCFD;
}
/**
* Closes the process environment.
*/
static int game_mode_close_proc(const procfd_t procfd)
{
return close(procfd);
}
/**
* Lookup the process environment for a specific variable or return NULL.
* Requires an open directory FD from /proc/PID.
*/
static char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var)
{
char *environ = NULL;
int fd = openat(proc_fd, "environ", O_RDONLY | O_CLOEXEC);
if (fd != -1) {
FILE *stream = fdopen(fd, "r");
if (stream) {
/* Read every \0 terminated line from the environment */
char *line = NULL;
size_t len = 0;
size_t pos = strlen(var) + 1;
while (!environ && (getdelim(&line, &len, 0, stream) != -1)) {
/* Find a match including the "=" suffix */
if ((len > pos) && (strncmp(line, var, strlen(var)) == 0) && (line[pos - 1] == '='))
environ = strndup(line + pos, len - pos);
}
free(line);
fclose(stream);
} else
close(fd);
}
/* If found variable is empty, skip it */
if (environ && !strlen(environ)) {
free(environ);
environ = NULL;
}
return environ;
}
/**
* Lookup the home directory of the user in a safe way.
*/
static char *game_mode_lookup_user_home(void)
{
/* Try loading env HOME first */
const char *home = secure_getenv("HOME");
if (!home) {
/* If HOME is not defined (or out of context), fall back to passwd */
struct passwd *pw = getpwuid(getuid());
if (!pw)
return NULL;
home = pw->pw_dir;
}
/* Try to allocate into our heap */
return home ? strdup(home) : NULL;
}
/**
* Attempt to resolve the exe for wine-preloader.
* This function is used if game_mode_context_find_exe() identified the
* process as wine-preloader. Returns NULL when resolve fails.
*/
char *game_mode_resolve_wine_preloader(const pid_t pid)
char *game_mode_resolve_wine_preloader(const char *exe, const pid_t pid)
{
/* Detect if the process is a wine loader process */
if (game_mode_detect_wine_preloader(exe) || game_mode_detect_wine_loader(exe)) {
LOG_MSG("Detected wine for client %d [%s].\n", pid, exe);
} else {
return NULL;
}
char buffer[PATH_MAX];
char *proc_path = NULL, *wine_exe = NULL, *wineprefix = NULL;
char *wine_exe = NULL, *wineprefix = NULL;
/* Open the directory, we are potentially reading multiple files from it */
procfd_t proc_fd = game_mode_open_proc(pid);
@ -141,9 +224,9 @@ char *game_mode_resolve_wine_preloader(const pid_t pid)
goto fail;
error_cleanup:
game_mode_close_proc(proc_fd);
if (proc_fd != INVALID_PROCFD)
game_mode_close_proc(proc_fd);
free(wineprefix);
free(proc_path);
return wine_exe;
fail:
@ -155,10 +238,10 @@ fail_cmdline:
goto error_cleanup;
fail_env:
LOG_ERROR("Failed to access process environment in '%s': %s\n", proc_path, strerror(errno));
LOG_ERROR("Failed to access process environment for client %d: %s\n", pid, strerror(errno));
goto error_cleanup;
fail_proc:
LOG_ERROR("Failed to access process data in '%s': %s\n", proc_path, strerror(errno));
LOG_ERROR("Failed to access process data for client %d: %s\n", pid, strerror(errno));
goto error_cleanup;
}

View File

@ -1,708 +0,0 @@
/*
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.
*/
#define _GNU_SOURCE
#include "gamemode.h"
#include "config.h"
#include "daemon_config.h"
#include "dbus_messaging.h"
#include "external-helper.h"
#include "governors-query.h"
#include "helpers.h"
#include "logging.h"
#include <pthread.h>
#include <signal.h>
#include <stdatomic.h>
#include <systemd/sd-daemon.h>
/**
* The GameModeClient encapsulates the remote connection, providing a list
* form to contain the pid and credentials.
*/
typedef struct GameModeClient {
pid_t pid; /**< Process ID */
struct GameModeClient *next; /**<Next client in the list */
char *executable; /**<Process executable */
} GameModeClient;
struct GameModeContext {
pthread_rwlock_t rwlock; /**<Guard access to the client list */
_Atomic int refcount; /**<Allow cycling the game mode */
GameModeClient *client; /**<Pointer to first client */
GameModeConfig *config; /**<Pointer to config object */
char initial_cpu_mode[64]; /**<Only updates when we can */
struct GameModeGPUInfo *stored_gpu; /**<Stored GPU info for the current GPU */
struct GameModeGPUInfo *target_gpu; /**<Target GPU info for the current GPU */
/* Reaper control */
struct {
pthread_t thread;
bool running;
pthread_mutex_t mutex;
pthread_cond_t condition;
} reaper;
};
static GameModeContext instance = { 0 };
/* Maximum number of concurrent processes we'll sanely support */
#define MAX_GAMES 256
/**
* Protect against signals
*/
static volatile bool had_context_init = false;
static GameModeClient *game_mode_client_new(pid_t pid, char *exe);
static void game_mode_client_free(GameModeClient *client);
static const GameModeClient *game_mode_context_has_client(GameModeContext *self, pid_t client);
static int game_mode_context_num_clients(GameModeContext *self);
static void *game_mode_context_reaper(void *userdata);
static void game_mode_context_enter(GameModeContext *self);
static void game_mode_context_leave(GameModeContext *self);
static char *game_mode_context_find_exe(pid_t pid);
static void game_mode_execute_scripts(char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX], int timeout);
void game_mode_context_init(GameModeContext *self)
{
if (had_context_init) {
LOG_ERROR("Context already initialised\n");
return;
}
had_context_init = true;
self->refcount = ATOMIC_VAR_INIT(0);
/* clear the initial string */
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
/* Initialise the config */
self->config = config_create();
config_init(self->config);
/* Initialise the current GPU info */
game_mode_initialise_gpu(self->config, &self->stored_gpu);
game_mode_initialise_gpu(self->config, &self->target_gpu);
pthread_rwlock_init(&self->rwlock, NULL);
pthread_mutex_init(&self->reaper.mutex, NULL);
pthread_cond_init(&self->reaper.condition, NULL);
/* Get the reaper thread going */
self->reaper.running = true;
if (pthread_create(&self->reaper.thread, NULL, game_mode_context_reaper, self) != 0) {
FATAL_ERROR("Couldn't construct a new thread");
}
}
void game_mode_context_destroy(GameModeContext *self)
{
if (!had_context_init) {
return;
}
/* Leave game mode now */
if (game_mode_context_num_clients(self) > 0) {
game_mode_context_leave(self);
}
had_context_init = false;
game_mode_client_free(self->client);
self->reaper.running = false;
/* We might be stuck waiting, so wake it up again */
pthread_mutex_lock(&self->reaper.mutex);
pthread_cond_signal(&self->reaper.condition);
pthread_mutex_unlock(&self->reaper.mutex);
/* Join the thread as soon as possible */
pthread_join(self->reaper.thread, NULL);
pthread_cond_destroy(&self->reaper.condition);
pthread_mutex_destroy(&self->reaper.mutex);
/* Destroy the gpu object */
game_mode_free_gpu(&self->stored_gpu);
game_mode_free_gpu(&self->target_gpu);
/* Destroy the config object */
config_destroy(self->config);
pthread_rwlock_destroy(&self->rwlock);
}
/**
* Pivot into game mode.
*
* This is only possible after game_mode_context_init has made a GameModeContext
* usable, and should always be followed by a game_mode_context_leave.
*/
static void game_mode_context_enter(GameModeContext *self)
{
LOG_MSG("Entering Game Mode...\n");
sd_notifyf(0, "STATUS=%sGameMode is now active.%s\n", "\x1B[1;32m", "\x1B[0m");
/* Read the initial governor state so we can revert it correctly */
const char *initial_state = get_gov_state();
if (initial_state) {
/* store the initial cpu governor mode */
strncpy(self->initial_cpu_mode, initial_state, sizeof(self->initial_cpu_mode) - 1);
self->initial_cpu_mode[sizeof(self->initial_cpu_mode) - 1] = '\0';
LOG_MSG("governor was initially set to [%s]\n", initial_state);
/* Choose the desired governor */
char desired[CONFIG_VALUE_MAX] = { 0 };
config_get_desired_governor(self->config, desired);
const char *desiredGov = desired[0] != '\0' ? desired : "performance";
const char *const exec_args[] = {
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", desiredGov, NULL,
};
LOG_MSG("Requesting update of governor policy to %s\n", desiredGov);
if (run_external_process(exec_args, NULL, -1) != 0) {
LOG_ERROR("Failed to update cpu governor policy\n");
/* if the set fails, clear the initial mode so we don't try and reset it back and fail
* again, presumably */
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
}
}
/* Inhibit the screensaver */
if (config_get_inhibit_screensaver(self->config))
game_mode_inhibit_screensaver(true);
/* Apply GPU optimisations by first getting the current values, and then setting the target */
game_mode_get_gpu(self->stored_gpu);
game_mode_apply_gpu(self->target_gpu);
/* Run custom scripts last - ensures the above are applied first and these scripts can react to
* them if needed */
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
memset(scripts, 0, sizeof(scripts));
config_get_gamemode_start_scripts(self->config, scripts);
long timeout = config_get_script_timeout(self->config);
game_mode_execute_scripts(scripts, (int)timeout);
}
/**
* Pivot out of game mode.
*
* Should only be called after both init and game_mode_context_enter have
* been performed.
*/
static void game_mode_context_leave(GameModeContext *self)
{
LOG_MSG("Leaving Game Mode...\n");
sd_notifyf(0, "STATUS=%sGameMode is currently deactivated.%s\n", "\x1B[1;36m", "\x1B[0m");
/* Remove GPU optimisations */
game_mode_apply_gpu(self->stored_gpu);
/* UnInhibit the screensaver */
if (config_get_inhibit_screensaver(self->config))
game_mode_inhibit_screensaver(false);
/* Reset the governer state back to initial */
if (self->initial_cpu_mode[0] != '\0') {
/* Choose the governor to reset to, using the config to override */
char defaultgov[CONFIG_VALUE_MAX] = { 0 };
config_get_default_governor(self->config, defaultgov);
const char *gov_mode = defaultgov[0] != '\0' ? defaultgov : self->initial_cpu_mode;
const char *const exec_args[] = {
"/usr/bin/pkexec", LIBEXECDIR "/cpugovctl", "set", gov_mode, NULL,
};
LOG_MSG("Requesting update of governor policy to %s\n", gov_mode);
if (run_external_process(exec_args, NULL, -1) != 0) {
LOG_ERROR("Failed to update cpu governor policy\n");
}
memset(self->initial_cpu_mode, 0, sizeof(self->initial_cpu_mode));
}
char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX];
memset(scripts, 0, sizeof(scripts));
config_get_gamemode_end_scripts(self->config, scripts);
long timeout = config_get_script_timeout(self->config);
game_mode_execute_scripts(scripts, (int)timeout);
}
/**
* Automatically expire all dead processes
*
* This has to take special care to ensure thread safety and ensuring that our
* pointer is never cached incorrectly.
*/
static void game_mode_context_auto_expire(GameModeContext *self)
{
bool removing = true;
while (removing) {
pthread_rwlock_rdlock(&self->rwlock);
removing = false;
/* Each time we hit an expired game, start the loop back */
for (GameModeClient *client = self->client; client; client = client->next) {
if (kill(client->pid, 0) != 0) {
LOG_MSG("Removing expired game [%i]...\n", client->pid);
pthread_rwlock_unlock(&self->rwlock);
game_mode_context_unregister(self, client->pid, client->pid);
removing = true;
break;
}
}
if (!removing) {
pthread_rwlock_unlock(&self->rwlock);
break;
}
if (game_mode_context_num_clients(self) == 0)
LOG_MSG("Properly cleaned up all expired games.\n");
}
}
/**
* Determine if the client is already known to the context
*/
static const GameModeClient *game_mode_context_has_client(GameModeContext *self, pid_t client)
{
const GameModeClient *found = NULL;
pthread_rwlock_rdlock(&self->rwlock);
/* Walk all clients and find a matching pid */
for (GameModeClient *cl = self->client; cl; cl = cl->next) {
if (cl->pid == client) {
found = cl;
break;
}
}
pthread_rwlock_unlock(&self->rwlock);
return found;
}
/**
* Helper to grab the current number of clients we know about
*/
static int game_mode_context_num_clients(GameModeContext *self)
{
return atomic_load(&self->refcount);
}
int game_mode_context_register(GameModeContext *self, pid_t client, pid_t requester)
{
errno = 0;
/* Construct a new client if we can */
GameModeClient *cl = NULL;
char *executable = NULL;
/* Check our requester config first */
if (requester != client) {
/* Lookup the executable first */
executable = game_mode_context_find_exe(requester);
if (!executable) {
return -1;
}
/* Check our blacklist and whitelist */
if (!config_get_supervisor_whitelisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
free(executable);
return -2;
} else if (config_get_supervisor_blacklisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
free(executable);
return -2;
}
} else if (config_get_require_supervisor(self->config)) {
LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
return -2;
}
/* Cap the total number of active clients */
if (game_mode_context_num_clients(self) + 1 > MAX_GAMES) {
LOG_ERROR("Max games (%d) reached, not registering %d\n", MAX_GAMES, client);
goto error_cleanup;
}
/* Check the PID first to spare a potentially expensive lookup for the exe */
pthread_rwlock_rdlock(&self->rwlock); // ensure our pointer is sane
const GameModeClient *existing = game_mode_context_has_client(self, client);
if (existing) {
LOG_HINTED(ERROR,
"Addition requested for already known client %d [%s].\n",
" -- This may happen due to using exec or shell wrappers. You may want to\n"
" -- blacklist this client so GameMode can see its final name here.\n",
existing->pid,
existing->executable);
pthread_rwlock_unlock(&self->rwlock);
goto error_cleanup;
}
pthread_rwlock_unlock(&self->rwlock);
/* Lookup the executable first */
executable = game_mode_context_find_exe(client);
if (!executable)
goto error_cleanup;
/* Check our blacklist and whitelist */
if (!config_get_client_whitelisted(self->config, executable)) {
LOG_MSG("Client [%s] was rejected (not in whitelist)\n", executable);
goto error_cleanup;
} else if (config_get_client_blacklisted(self->config, executable)) {
LOG_MSG("Client [%s] was rejected (in blacklist)\n", executable);
goto error_cleanup;
}
/* From now on we depend on the client, initialize it */
cl = game_mode_client_new(client, executable);
if (cl)
executable = NULL; // ownership has been delegated
else
goto error_cleanup;
/* Begin a write lock now to insert our new client at list start */
pthread_rwlock_wrlock(&self->rwlock);
LOG_MSG("Adding game: %d [%s]\n", client, cl->executable);
/* Update the list */
cl->next = self->client;
self->client = cl;
pthread_rwlock_unlock(&self->rwlock);
/* First add, init */
if (atomic_fetch_add_explicit(&self->refcount, 1, memory_order_seq_cst) == 0) {
game_mode_context_enter(self);
}
/* Apply scheduler policies */
game_mode_apply_renice(self, client);
game_mode_apply_scheduling(self, client);
/* Apply io priorities */
game_mode_apply_ioprio(self, client);
return 0;
error_cleanup:
if (errno != 0)
LOG_ERROR("Failed to register client [%d]: %s\n", client, strerror(errno));
free(executable);
game_mode_client_free(cl);
return -1;
}
int game_mode_context_unregister(GameModeContext *self, pid_t client, pid_t requester)
{
GameModeClient *cl = NULL;
GameModeClient *prev = NULL;
bool found = false;
/* Check our requester config first */
if (requester != client) {
/* Lookup the executable first */
char *executable = game_mode_context_find_exe(requester);
if (!executable) {
return -1;
}
/* Check our blacklist and whitelist */
if (!config_get_supervisor_whitelisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
free(executable);
return -2;
} else if (config_get_supervisor_blacklisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
free(executable);
return -2;
}
free(executable);
} else if (config_get_require_supervisor(self->config)) {
LOG_ERROR("Direct request made but require_supervisor was set, rejecting request!\n");
return -2;
}
/* Requires locking. */
pthread_rwlock_wrlock(&self->rwlock);
for (prev = cl = self->client; cl; cl = cl->next) {
if (cl->pid != client) {
prev = cl;
continue;
}
LOG_MSG("Removing game: %d [%s]\n", client, cl->executable);
/* Found it */
found = true;
prev->next = cl->next;
if (cl == self->client) {
self->client = cl->next;
}
cl->next = NULL;
game_mode_client_free(cl);
break;
}
/* Unlock here, potentially yielding */
pthread_rwlock_unlock(&self->rwlock);
if (!found) {
LOG_HINTED(
ERROR,
"Removal requested for unknown process [%d].\n",
" -- The parent process probably forked and tries to unregister from the wrong\n"
" -- process now. We cannot work around this. This message will likely be paired\n"
" -- with a nearby 'Removing expired game' which means we cleaned up properly\n"
" -- (we will log this event). This hint will be displayed only once.\n",
client);
return -1;
}
/* When we hit bottom then end the game mode */
if (atomic_fetch_sub_explicit(&self->refcount, 1, memory_order_seq_cst) == 1) {
game_mode_context_leave(self);
}
return 0;
}
int game_mode_context_query_status(GameModeContext *self, pid_t client, pid_t requester)
{
GameModeClient *cl = NULL;
int ret = 0;
/* First check the requester settings if appropriate */
if (client != requester) {
char *executable = game_mode_context_find_exe(requester);
if (!executable) {
return -1;
}
/* Check our blacklist and whitelist */
if (!config_get_supervisor_whitelisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (not in whitelist)\n", executable);
free(executable);
return -2;
} else if (config_get_supervisor_blacklisted(self->config, executable)) {
LOG_MSG("Supervisor [%s] was rejected (in blacklist)\n", executable);
free(executable);
return -2;
}
free(executable);
}
/*
* Check the current refcount on gamemode, this equates to whether gamemode is active or not,
* see game_mode_context_register and game_mode_context_unregister
*/
if (atomic_load_explicit(&self->refcount, memory_order_seq_cst)) {
ret++;
/* Check if the current client is registered */
/* Requires locking. */
pthread_rwlock_rdlock(&self->rwlock);
for (cl = self->client; cl; cl = cl->next) {
if (cl->pid != client) {
continue;
}
/* Found it */
ret++;
break;
}
/* Unlock here, potentially yielding */
pthread_rwlock_unlock(&self->rwlock);
}
return ret;
}
/**
* Construct a new GameModeClient for the given process ID
*
* This is deliberately OOM safe
*/
static GameModeClient *game_mode_client_new(pid_t pid, char *executable)
{
GameModeClient c = {
.executable = executable,
.next = NULL,
.pid = pid,
};
GameModeClient *ret = NULL;
ret = calloc(1, sizeof(struct GameModeClient));
if (!ret) {
return NULL;
}
*ret = c;
return ret;
}
/**
* Free a client and the next element in the list.
*/
static void game_mode_client_free(GameModeClient *client)
{
if (!client) {
return;
}
if (client->next) {
game_mode_client_free(client->next);
}
if (client->executable) {
free(client->executable);
}
free(client);
}
/**
* We continuously run until told otherwise.
*/
static void *game_mode_context_reaper(void *userdata)
{
/* Stack, not allocated, won't disappear. */
GameModeContext *self = userdata;
long reaper_interval = config_get_reaper_frequency(self->config);
struct timespec ts = { 0, 0 };
ts.tv_sec = time(NULL) + reaper_interval;
while (self->reaper.running) {
/* Wait for condition */
pthread_mutex_lock(&self->reaper.mutex);
pthread_cond_timedwait(&self->reaper.condition, &self->reaper.mutex, &ts);
pthread_mutex_unlock(&self->reaper.mutex);
/* Highly possible the main thread woke us up to exit */
if (!self->reaper.running) {
return NULL;
}
/* Expire remaining entries */
game_mode_context_auto_expire(self);
ts.tv_sec = time(NULL) + reaper_interval;
}
return NULL;
}
GameModeContext *game_mode_context_instance()
{
return &instance;
}
GameModeConfig *game_mode_config_from_context(const GameModeContext *context)
{
return context ? context->config : NULL;
}
/**
* Attempt to locate the exe for the process.
* We might run into issues if the process is running under an odd umask.
*/
static char *game_mode_context_find_exe(pid_t pid)
{
char buffer[PATH_MAX];
char *proc_path = NULL, *wine_exe = NULL;
if (!(proc_path = buffered_snprintf(buffer, "/proc/%d/exe", pid)))
goto fail;
/* Allocate the realpath if possible */
char *exe = realpath(proc_path, NULL);
if (!exe)
goto fail;
/* Detect if the process is a wine loader process */
if (game_mode_detect_wine_preloader(exe)) {
LOG_MSG("Detected wine preloader for client %d [%s].\n", pid, exe);
goto wine_preloader;
}
if (game_mode_detect_wine_loader(exe)) {
LOG_MSG("Detected wine loader for client %d [%s].\n", pid, exe);
goto wine_preloader;
}
return exe;
wine_preloader:
wine_exe = game_mode_resolve_wine_preloader(pid);
if (wine_exe) {
free(exe);
exe = wine_exe;
return exe;
}
/* We have to ignore this because the wine process is in some sort
* of respawn mode
*/
free(exe);
fail:
if (errno != 0) // otherwise a proper message was logged before
LOG_ERROR("Unable to find executable for PID %d: %s\n", pid, strerror(errno));
return NULL;
}
/* Executes a set of scripts */
static void game_mode_execute_scripts(char scripts[CONFIG_LIST_MAX][CONFIG_VALUE_MAX], int timeout)
{
unsigned int i = 0;
while (*scripts[i] != '\0' && i < CONFIG_LIST_MAX) {
LOG_MSG("Executing script [%s]\n", scripts[i]);
int err;
const char *args[] = { "/bin/sh", "-c", scripts[i], NULL };
if ((err = run_external_process(args, NULL, timeout)) != 0) {
/* Log the failure, but this is not fatal */
LOG_ERROR("Script [%s] failed with error %d\n", scripts[i], err);
}
i++;
}
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h>
#define INVALID_PROCFD -1
@ -43,6 +44,41 @@ typedef int procfd_t;
*/
typedef struct GameModeContext GameModeContext;
typedef struct GameModeConfig GameModeConfig;
typedef struct GameModeClient GameModeClient;
/**
* GameModeClient related functions
*/
/**
* Decrement the usage count of client.
*/
void game_mode_client_unref(GameModeClient *client);
/**
* Increment the usage count of client.
*/
void game_mode_client_ref(GameModeClient *client);
/**
* The process identifier of the client.
*/
pid_t game_mode_client_get_pid(GameModeClient *client);
/**
* The path to the executable of client.
*/
const char *game_mode_client_get_executable(GameModeClient *client);
/**
* The process identifier of the requester.
*/
pid_t game_mode_client_get_requester(GameModeClient *client);
/**
* The time that game mode was requested for the client.
*/
u_int64_t game_mode_client_get_timestamp(GameModeClient *client);
/**
* Return the singleton instance
@ -63,6 +99,29 @@ void game_mode_context_init(GameModeContext *self);
*/
void game_mode_context_destroy(GameModeContext *self);
/**
* Query the number of currently registered clients.
*
* @returns The number of clients. A number > 0 means that gamemode is active.
*/
int game_mode_context_num_clients(GameModeContext *self);
/**
* List the currently active clients.
* @param out holds the number of active clients.
*
* @returns A array of pid_t or NULL if there are no active clients.
*/
pid_t *game_mode_context_list_clients(GameModeContext *self, unsigned int *count);
/**
* Lookup up information about a client via the pid;
*
* @returns A pointer to a GameModeClient struct or NULL in case no client
* with the corresponding id could be found. Adds a reference to
* GameModeClient that needs to be released.
*/
GameModeClient *game_mode_context_lookup_client(GameModeContext *self, pid_t client);
/**
* Register a new game client with the context
*
@ -104,40 +163,31 @@ int game_mode_context_query_status(GameModeContext *self, pid_t pid, pid_t reque
*/
GameModeConfig *game_mode_config_from_context(const GameModeContext *context);
/** gamemode-env.c
* Provides internal API functions specific to working environment
* variables.
/*
* Reload the current configuration
*/
char *game_mode_lookup_proc_env(const procfd_t proc_fd, const char *var);
char *game_mode_lookup_user_home(void);
int game_mode_reload_config(GameModeContext *context);
/** gamemode-ioprio.c
* Provides internal API functions specific to adjusting process
* IO priorities.
*/
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client);
/** gamemode-proc.c
* Provides internal API functions specific to working with process
* environments.
*/
procfd_t game_mode_open_proc(const pid_t pid);
int game_mode_close_proc(const procfd_t procfd);
int game_mode_get_ioprio(const pid_t client);
void game_mode_apply_ioprio(const GameModeContext *self, const pid_t client, int expected);
/** gamemode-sched.c
* Provides internal API functions specific to adjusting process
* scheduling.
*/
void game_mode_apply_renice(const GameModeContext *self, const pid_t client);
int game_mode_get_renice(const pid_t client);
void game_mode_apply_renice(const GameModeContext *self, const pid_t client, int expected);
void game_mode_apply_scheduling(const GameModeContext *self, const pid_t client);
/** gamemode-wine.c
* Provides internal API functions specific to handling wine
* prefixes.
*/
bool game_mode_detect_wine_loader(const char *exe);
bool game_mode_detect_wine_preloader(const char *exe);
char *game_mode_resolve_wine_preloader(const pid_t pid);
char *game_mode_resolve_wine_preloader(const char *exe, const pid_t pid);
/** gamemode-tests.c
* Provides a test suite to verify gamemode behaviour
@ -152,3 +202,26 @@ int game_mode_initialise_gpu(GameModeConfig *config, GameModeGPUInfo **info);
void game_mode_free_gpu(GameModeGPUInfo **info);
int game_mode_apply_gpu(const GameModeGPUInfo *info);
int game_mode_get_gpu(GameModeGPUInfo *info);
/** gamemode-cpu.c
* Provides internal functions to apply optimisations to cpus
*/
typedef struct GameModeCPUInfo GameModeCPUInfo;
int game_mode_initialise_cpu(GameModeConfig *config, GameModeCPUInfo **info);
void game_mode_free_cpu(GameModeCPUInfo **info);
void game_mode_reconfig_cpu(GameModeConfig *config, GameModeCPUInfo **info);
int game_mode_park_cpu(const GameModeCPUInfo *info);
int game_mode_unpark_cpu(const GameModeCPUInfo *info);
void game_mode_apply_core_pinning(const GameModeCPUInfo *info, const pid_t client,
const bool be_silent);
void game_mode_undo_core_pinning(const GameModeCPUInfo *info, const pid_t client);
/** gamemode-dbus.c
* Provides an API interface for using dbus
*/
typedef struct GameModeIdleInhibitor GameModeIdleInhibitor;
void game_mode_context_loop(GameModeContext *context) __attribute__((noreturn));
GameModeIdleInhibitor *game_mode_create_idle_inhibitor(void);
void game_mode_destroy_idle_inhibitor(GameModeIdleInhibitor *inhibitor);
void game_mode_client_registered(pid_t);
void game_mode_client_unregistered(pid_t);

341
daemon/gamemoded.c Normal file
View File

@ -0,0 +1,341 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
/**
* Simple daemon to allow user space programs to control the CPU governors
*
* The main process is responsible for bootstrapping the D-BUS daemon, caching
* the initial governor settings, and then responding to requests over D-BUS.
*
* Clients register their pid(s) with the service, which are routinely checked
* to see if they've expired. Once we reach our first actively registered client
* we put the system into "game mode", i.e. move the CPU governor into a performance
* mode.
*
* Upon exit, or when all clients have stopped running, we put the system back
* into the default governor policy, which is invariably powersave or similar
* on laptops. This ensures that the system is obtaining the maximum performance
* whilst gaming, and allowed to sanely return to idle once the workload is
* complete.
*/
#define _GNU_SOURCE
#include "gamemode.h"
#include "common-logging.h"
#include "gamemode-config.h"
#include "gamemode_client.h"
#include "build-config.h"
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <sys/stat.h>
#include <systemd/sd-daemon.h> /* TODO: Move usage to gamemode-dbus.c */
#include <unistd.h>
#define USAGE_TEXT \
"Usage: %s [-d] [-l] [-r] [-t] [-h] [-v]\n\n" \
" -r[PID], --request=[PID] Toggle gamemode for process\n" \
" When no PID given, requests gamemode and pauses\n" \
" -s[PID], --status=[PID] Query the status of gamemode for process\n" \
" When no PID given, queries the status globally\n" \
" -d, --daemonize Daemonize self after launch\n" \
" -l, --log-to-syslog Log to syslog\n" \
" -t, --test Run tests\n" \
" -h, --help Print this help\n" \
" -v, --version Print version\n" \
"\n" \
"See man page for more information.\n"
#define VERSION_TEXT "gamemode version: v" GAMEMODE_VERSION "\n"
static void sigint_handler(__attribute__((unused)) int signo)
{
LOG_MSG("Quitting by request...\n");
sd_notify(0, "STATUS=GameMode is quitting by request...\n");
/* Clean up nicely */
game_mode_context_destroy(game_mode_context_instance());
_Exit(EXIT_SUCCESS);
}
static void sigint_handler_noexit(__attribute__((unused)) int signo)
{
LOG_MSG("Quitting by request...\n");
}
/**
* Helper to perform standard UNIX daemonization
*/
static void daemonize(const char *name)
{
/* Initial fork */
pid_t pid = fork();
if (pid < 0) {
FATAL_ERRORNO("Failed to fork");
}
if (pid != 0) {
LOG_MSG("Daemon launched as %s...\n", name);
exit(EXIT_SUCCESS);
}
/* Fork a second time */
pid = fork();
if (pid < 0) {
FATAL_ERRORNO("Failed to fork");
} else if (pid > 0) {
exit(EXIT_SUCCESS);
}
/* Now continue execution */
umask(0022);
if (setsid() < 0) {
FATAL_ERRORNO("Failed to create process group\n");
}
if (chdir("/") < 0) {
FATAL_ERRORNO("Failed to change to root directory\n");
}
/* replace standard file descriptors by /dev/null */
int devnull_r = open("/dev/null", O_RDONLY);
int devnull_w = open("/dev/null", O_WRONLY);
if (devnull_r == -1 || devnull_w == -1) {
LOG_ERROR("Failed to redirect standard input and output to /dev/null\n");
} else {
dup2(devnull_r, STDIN_FILENO);
dup2(devnull_w, STDOUT_FILENO);
dup2(devnull_w, STDERR_FILENO);
close(devnull_r);
close(devnull_w);
}
}
/**
* Main bootstrap entry into gamemoded
*/
int main(int argc, char *argv[])
{
GameModeContext *context = NULL;
/* Gather command line options */
bool daemon = false;
bool use_syslog = false;
int opt = 0;
/* Options struct for getopt_long */
static struct option long_options[] = {
{ "daemonize", no_argument, 0, 'd' }, { "log-to-syslog", no_argument, 0, 'l' },
{ "request", optional_argument, 0, 'r' }, { "test", no_argument, 0, 't' },
{ "status", optional_argument, 0, 's' }, { "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'v' }, { NULL, 0, NULL, 0 },
};
static const char *short_options = "dls::r::tvh";
while ((opt = getopt_long(argc, argv, short_options, long_options, 0)) != -1) {
switch (opt) {
case 'd':
daemon = true;
break;
case 'l':
use_syslog = true;
break;
case 's':
if (optarg != NULL) {
pid_t pid = atoi(optarg);
switch (gamemode_query_status_for(pid)) {
case 0: /* inactive */
LOG_MSG("gamemode is inactive\n");
break;
case 1: /* active not not registered */
LOG_MSG("gamemode is active but [%d] not registered\n", pid);
break;
case 2: /* active for client */
LOG_MSG("gamemode is active and [%d] registered\n", pid);
break;
case -1:
LOG_ERROR("gamemode_query_status_for(%d) failed: %s\n",
pid,
gamemode_error_string());
exit(EXIT_FAILURE);
default:
LOG_ERROR("gamemode_query_status returned unexpected value 2\n");
exit(EXIT_FAILURE);
}
} else {
int ret = 0;
switch ((ret = gamemode_query_status())) {
case 0: /* inactive */
LOG_MSG("gamemode is inactive\n");
break;
case 1: /* active */
LOG_MSG("gamemode is active\n");
break;
case -1: /* error */
LOG_ERROR("gamemode status request failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
default: /* unexpected value eg. 2 */
LOG_ERROR("gamemode_query_status returned unexpected value %d\n", ret);
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
case 'r':
if (optarg != NULL) {
pid_t pid = atoi(optarg);
/* toggle gamemode for the process */
switch (gamemode_query_status_for(pid)) {
case 0: /* inactive */
case 1: /* active not not registered */
LOG_MSG("gamemode not active for client, requesting start for %d...\n", pid);
if (gamemode_request_start_for(pid) < 0) {
LOG_ERROR("gamemode_request_start_for(%d) failed: %s\n",
pid,
gamemode_error_string());
exit(EXIT_FAILURE);
}
LOG_MSG("request succeeded\n");
break;
case 2: /* active for client */
LOG_MSG("gamemode active for client, requesting end for %d...\n", pid);
if (gamemode_request_end_for(pid) < 0) {
LOG_ERROR("gamemode_request_end_for(%d) failed: %s\n",
pid,
gamemode_error_string());
exit(EXIT_FAILURE);
}
LOG_MSG("request succeeded\n");
break;
case -1: /* error */
LOG_ERROR("gamemode_query_status_for(%d) failed: %s\n",
pid,
gamemode_error_string());
exit(EXIT_FAILURE);
}
} else {
/* Request gamemode for this process */
if (gamemode_request_start() < 0) {
LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
}
/* Request and report on the status */
switch (gamemode_query_status()) {
case 2: /* active for this client */
LOG_MSG("gamemode request succeeded and is active\n");
break;
case 1: /* active */
LOG_ERROR("gamemode request succeeded and is active but registration failed\n");
exit(EXIT_FAILURE);
case 0: /* inactive */
LOG_ERROR("gamemode request succeeded but is not active\n");
exit(EXIT_FAILURE);
case -1: /* error */
LOG_ERROR("gamemode_query_status failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
}
/* Simply pause and wait a SIGINT */
if (signal(SIGINT, sigint_handler_noexit) == SIG_ERR) {
FATAL_ERRORNO("Could not catch SIGINT");
}
pause();
/* Explicitly clean up */
if (gamemode_request_end() < 0) {
LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
case 't': {
int status = game_mode_run_client_tests();
exit(status);
}
case 'v':
LOG_MSG(VERSION_TEXT);
exit(EXIT_SUCCESS);
case 'h':
LOG_MSG(USAGE_TEXT, argv[0]);
exit(EXIT_SUCCESS);
default:
fprintf(stderr, USAGE_TEXT, argv[0]);
exit(EXIT_FAILURE);
}
}
/* If syslog is requested, set it up with our process name */
if (use_syslog) {
set_use_syslog(argv[0]);
}
/* Daemonize ourselves first if asked */
if (daemon) {
daemonize(argv[0]);
}
/* Log a version message on startup */
LOG_MSG("v%s\n", GAMEMODE_VERSION);
/* Set up the game mode context */
context = game_mode_context_instance();
game_mode_context_init(context);
/* Handle quits cleanly */
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
FATAL_ERRORNO("Could not catch SIGINT");
}
if (signal(SIGTERM, sigint_handler) == SIG_ERR) {
FATAL_ERRORNO("Could not catch SIGTERM");
}
/* Run the main dbus message loop */
game_mode_context_loop(context);
game_mode_context_destroy(context);
/* Log we're finished */
LOG_MSG("Quitting naturally...\n");
sd_notify(0, "STATUS=GameMode is quitting naturally...\n");
}

View File

@ -1,209 +0,0 @@
/*
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.
*/
/**
* Simple daemon to allow user space programs to control the CPU governors
*
* The main process is responsible for bootstrapping the D-BUS daemon, caching
* the initial governor settings, and then responding to requests over D-BUS.
*
* Clients register their pid(s) with the service, which are routinely checked
* to see if they've expired. Once we reach our first actively registered client
* we put the system into "game mode", i.e. move the CPU governor into a performance
* mode.
*
* Upon exit, or when all clients have stopped running, we put the system back
* into the default governor policy, which is invariably powersave or similar
* on laptops. This ensures that the system is obtaining the maximum performance
* whilst gaming, and allowed to sanely return to idle once the workload is
* complete.
*/
#define _GNU_SOURCE
#include "config.h"
#include "daemonize.h"
#include "dbus_messaging.h"
#include "gamemode.h"
#include "gamemode_client.h"
#include "logging.h"
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <systemd/sd-daemon.h>
#include <unistd.h>
#define USAGE_TEXT \
"Usage: %s [-d] [-l] [-r] [-t] [-h] [-v]\n\n" \
" -d daemonize self after launch\n" \
" -l log to syslog\n" \
" -r request gamemode and pause\n" \
" -t run tests\n" \
" -h print this help\n" \
" -v print version\n" \
"\n" \
"See man page for more information.\n"
#define VERSION_TEXT "gamemode version: v" GAMEMODE_VERSION "\n"
static void sigint_handler(__attribute__((unused)) int signo)
{
LOG_MSG("Quitting by request...\n");
sd_notify(0, "STATUS=GameMode is quitting by request...\n");
/* Clean up nicely */
game_mode_context_destroy(game_mode_context_instance());
_Exit(EXIT_SUCCESS);
}
static void sigint_handler_noexit(__attribute__((unused)) int signo)
{
LOG_MSG("Quitting by request...\n");
}
/**
* Main bootstrap entry into gamemoded
*/
int main(int argc, char *argv[])
{
GameModeContext *context = NULL;
/* Gather command line options */
bool daemon = false;
bool use_syslog = false;
int opt = 0;
int status;
while ((opt = getopt(argc, argv, "dlsrtvh")) != -1) {
switch (opt) {
case 'd':
daemon = true;
break;
case 'l':
use_syslog = true;
break;
case 's': {
if ((status = gamemode_query_status()) < 0) {
LOG_ERROR("gamemode status request failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
} else if (status > 0) {
LOG_MSG("gamemode is active\n");
} else {
LOG_MSG("gamemode is inactive\n");
}
exit(EXIT_SUCCESS);
break;
}
case 'r':
if (gamemode_request_start() < 0) {
LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
}
if ((status = gamemode_query_status()) == 2) {
LOG_MSG("gamemode request succeeded and is active\n");
} else if (status == 1) {
LOG_ERROR("gamemode request succeeded and is active but registration failed\n");
exit(EXIT_FAILURE);
} else {
LOG_ERROR("gamemode request succeeded but is not active\n");
exit(EXIT_FAILURE);
}
// Simply pause and wait a SIGINT
if (signal(SIGINT, sigint_handler_noexit) == SIG_ERR) {
FATAL_ERRORNO("Could not catch SIGINT");
}
pause();
// Explicitly clean up
if (gamemode_request_end() < 0) {
LOG_ERROR("gamemode request failed: %s\n", gamemode_error_string());
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
break;
case 't':
status = game_mode_run_client_tests();
exit(status);
break;
case 'v':
LOG_MSG(VERSION_TEXT);
exit(EXIT_SUCCESS);
break;
case 'h':
LOG_MSG(USAGE_TEXT, argv[0]);
exit(EXIT_SUCCESS);
break;
default:
fprintf(stderr, USAGE_TEXT, argv[0]);
exit(EXIT_FAILURE);
break;
}
}
/* If syslog is requested, set it up with our process name */
if (use_syslog) {
set_use_syslog(argv[0]);
}
/* Daemonize ourselves first if asked */
if (daemon) {
daemonize(argv[0]);
}
/* Log a version message on startup */
LOG_MSG("v%s\n", GAMEMODE_VERSION);
/* Set up the game mode context */
context = game_mode_context_instance();
game_mode_context_init(context);
/* Handle quits cleanly */
if (signal(SIGINT, sigint_handler) == SIG_ERR) {
FATAL_ERRORNO("Could not catch SIGINT");
}
if (signal(SIGTERM, sigint_handler) == SIG_ERR) {
FATAL_ERRORNO("Could not catch SIGTERM");
}
/* Run the main dbus message loop */
game_mode_context_loop(context);
game_mode_context_destroy(context);
/* Log we're finished */
LOG_MSG("Quitting naturally...\n");
sd_notify(0, "STATUS=GameMode is quitting naturally...\n");
}

View File

@ -1,80 +1,40 @@
# Convenience library for the duplicated logging functionality
common_sources = [
'logging.c',
'governors-query.c',
'external-helper.c',
'gpu-control.c',
]
daemon_common = static_library(
'daemon-common',
sources: common_sources,
install: false,
)
link_daemon_common = declare_dependency(
link_with: daemon_common,
)
# Main daemon
daemon_sources = [
'main.c',
'gamemode.c',
'gamemode-env.c',
'gamemoded.c',
'gamemode-context.c',
'gamemode-ioprio.c',
'gamemode-proc.c',
'gamemode-sched.c',
'gamemode-wine.c',
'gamemode-tests.c',
'gamemode-gpu.c',
'daemonize.c',
'dbus_messaging.c',
'daemon_config.c',
'gamemode-cpu.c',
'gamemode-dbus.c',
'gamemode-config.c',
]
gamemoded_includes = libgamemode_includes
gamemoded_includes = gamemode_headers_includes
gamemoded_includes += config_h_dir
executable(
gamemoded = executable(
'gamemoded',
sources: daemon_sources,
c_args: sd_bus_args,
dependencies: [
link_daemon_common,
dep_threads,
dep_systemd,
sd_bus_dep,
inih_dependency,
libdl,
],
include_directories: gamemoded_includes,
install: true,
)
# Small target util to get and set cpu governors
cpugovctl_sources = [
'cpugovctl.c',
]
cpugovctl = executable(
'cpugovctl',
sources: cpugovctl_sources,
dependencies: [
link_daemon_common,
include_directories: [
gamemoded_includes,
],
install: true,
install_dir: path_libexecdir,
)
# Small target util to get and set gpu clocks values
gpuclockctl_sources = [
'gpuclockctl.c',
]
gpuclockctl = executable(
'gpuclockctl',
sources: gpuclockctl_sources,
dependencies: [
link_daemon_common,
],
install: true,
install_dir: path_libexecdir,
# verify gamemoded compiled properly
test(
'validate gamemoded compiled properly',
gamemoded,
args: ['-v'],
)

View File

@ -1 +0,0 @@
@@LIMITSGROUP@ - nice -10

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
<!--
Copyright (c) 2017-2019, Feral Interactive
All rights reserved.
-->
<vendor>Feral GameMode Activation</vendor>
<vendor_url>http://www.feralinteractive.com</vendor_url>
<action id="com.feralinteractive.GameMode.governor-helper">
<description>Modify the CPU governor</description>
<message>Authentication is required to modify the CPU governor</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/cpugovctl</annotate>
</action>
<action id="com.feralinteractive.GameMode.gpu-helper">
<description>Modify the GPU clock states</description>
<message>Authentication is required to modify the GPU clock states</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>yes</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/gpuclockctl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>

View File

@ -0,0 +1,33 @@
.\" Manpage for gamemode-simulate-game.
.\" Contact linux-contact@feralinteractive.com to correct errors or typos.
.TH gamemode-simulate-game 1 "26 May 2020" "@GAMEMODE_VERSION@" "gamemode-simulate-game man page"
.SH NAME
gamemode-simulate-game \- simulate a game using gamemode
.SH SYNOPSIS
\fBgamemode-simulate-game\fR
.SH DESCRIPTION
\fBGameMode\fR is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS.
The design has a clear cut abstraction between the host daemon and library (\fBgamemoded\fR and \fBlibgamemode\fR), and the client loaders (\fBlibgamemodeauto\fR and \fBgamemode_client.h\fR) that allows for safe usage without worrying about whether the daemon is installed or running. This design also means that while the host library currently relies on systemd for exchanging messages with the daemon, it's entirely possible to implement other internals that still work with the same clients.
\fBGameMode\fR was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU governor states, as there are a wealth of automation tasks one might want to apply.
.SH USAGE
The executable starts gamemode, sleeps for 10 seconds and stops it. It will exit with zero if everything works fine, else it will print an error and exit with one.
To use this with a CI you might need to start a dbus session by hand. This can be done with:
.RS 4
dbus-run-session -- gamemode-simulate-game
.RE
Note that this might output to stderr, even if it exits with zero.
.SH SEE ALSO
gamemoded(8), gamemoderun(1), dbus-run-session(1)
.SH ABOUT
GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR
.SH AUTHOR
Feral Interactive (linux-contact@feralinteractive.com)

View File

@ -1,10 +1,10 @@
.\" Manpage for gamemoded.
.\" Contact linux-contact@feralinteractive.com to correct errors or typos.
.TH gamemoded 8 "15 March 2019" "1.3" "gamemoded man page"
.TH gamemoded 8 "4 May 2020" "@GAMEMODE_VERSION@" "gamemoded man page"
.SH NAME
gamemoded \- optimises system performance on demand
gamemoded \- daemon that optimises system performance on demand
.SH SYNOPSIS
\fBgamemoded\fR [\fB\-d\fR] [\fB\-l\fR] [\fB\-h\fR] [\fB\-v\fR]
\fBgamemoded\fR [OPTIONS...]
.SH DESCRIPTION
\fBGameMode\fR is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS.
@ -12,46 +12,33 @@ The design has a clear cut abstraction between the host daemon and library (\fBg
\fBGameMode\fR was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU governor states, as there are a wealth of automation tasks one might want to apply.
.SH OPTIONS
.TP 8
.B \-d
.B \-r[PID], \-\-request=[PID]
Toggle gamemode for process.
When no PID given, requests gamemode and pauses
.TP 8
.B \-s[PID], \-\-status=[PID]
Query the status of gamemode for process
When no PID given, queries the status globally
.TP 8
.B \-d, \-\-daemonize
Run the daemon as a separate process (daemonize it)
.TP 8
.B \-l
.B \-l, \-\-log-to-syslog
Log to syslog
.TP 8
.B \-r
Request gamemode and wait for any signal
.TP 8
.B \-s
Query the current status of gamemode
.TP 8
.B \-h
.TP 8
.B \-h, \-\-help
Print help text
.TP 8
.B \-t
.B \-t, \-\-test
Run diagnostic tests on the current installation
.TP 8
.B \-v
.B \-v, \-\-version
Print the version
.SH USAGE
\fBlibgamemodeauto.so.0\fR can be pre-loaded into any program to request \fBgamemoded\fR begin or end the mode, like so:
.RS 4
gamemoderun \./game
.RE
Or by setting the steam launch options for a game:
.RS 4
gamemoderun %command%
.RE
The library can be manually preloaded if needed:
.RS 4
LD_PRELOAD=$LD_PRELOAD:/usr/\e$LIB/libgamemodeauto.so.0 ./game
.RE
\fBlibgamemodeauto.so.0\fR can be pre-loaded into any program to request \fBgamemoded\fR begin or end the mode. See gamemoderun(1) for details.
The \fBgamemode_client.h\fR header can be used by developers to build the requests into a program:
@ -86,7 +73,7 @@ Or, distribute \fBlibgamemodeauto.so.0\fR and either link with \fB\-lgamemodeaut
\fBgamemoded\fR will load and merge \fBgamemode.ini\fR config files from these directories in the following order:
.RS 4
/usr/share/gamemode/
@SYSCONFDIR@/
.RE
.RS 4
/etc/
@ -107,7 +94,7 @@ Behaviour of the config file can be explained by presenting a commented example:
.RE
.SH SEE ALSO
systemd(1)
gamemoderun(1), systemd(1)
.SH ABOUT
GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR

32
data/gamemodelist Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
# Created by Sam Gleske
# Created Sat Jan 1 16:56:54 EST 2022
# MIT License - https://github.com/samrocketman/home
# DESCRIPTION
# Find all running processes which have loaded Feral Interactive gamemode
# via libgamemodeauto.so. This script will not detect processes which load
# gamemode without libgamemodeauto.so.
# DEVELOPMENT ENVIRONMENT
# Ubuntu 18.04.6 LTS
# Linux 5.4.0-91-generic x86_64
# GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
# find (GNU findutils) 4.7.0-git
# GNU Awk 4.1.4, API: 1.1 (GNU MPFR 4.0.1, GNU MP 6.1.2)
# xargs (GNU findutils) 4.7.0-git
# ps from procps-ng 3.3.12
if [ -z "${USER:-}" ]; then
echo '$USER variable not defined.' >&2
exit 1
fi
if [ ! -d /proc ]; then
echo 'ERROR: /proc filesystem missing. We do not appear to be running on Linux.' >&2
exit 1
fi
find /proc -maxdepth 2 -type f -user "${USER}" -readable -name maps -exec \
awk -- 'BEGINFILE { if (ERRNO) nextfile } $0 ~ /libgamemodeauto\.so\.0/ {pid=FILENAME; gsub("[^0-9]", "", pid); print pid;nextfile}' {} + \
| xargs | xargs -I{} -- ps -o pid,ppid,user,ni,psr,comm --pid '{}'

68
data/gamemodelist.1.in Normal file
View File

@ -0,0 +1,68 @@
.\" Manpage for gamemodelist.
.\" Contact linux-contact@feralinteractive.com to correct errors or typos.
.TH gamemodelist 1 "4 May 2020" "@GAMEMODE_VERSION@" "gamemodelist man page"
.SH NAME
gamemodelist \- search for processes running with gamemode
.SH SYNOPSIS
\fBgamemodelist\fR
.SH DESCRIPTION
\fBgamemodelist\fR will search the runtime of all processes running which started \fBGameMode\fR via \fBlibgamemodeauto.so\fR and print them with
.BR ps (1)
command output. This helper script makes it easy to find which processes are utilizing \fBGameMode\fR via \fBlibgamemodeauto.so\fR when troubleshooting potential game issues.
.SH USAGE
\fBlibgamemodeauto.so.0\fR will be found in the shared object maps of running processes utilizing \fBGameMode\fR. Run the following command to print processes loaded with \fBlibgamemodeauto.so.0\fR. \fBGameMode\fR can be started other ways but this script will only detect processes utilizing \fBGameMode\fR via \fBlibgamemodeauto.so\fR.
.RS 4
gamemodelist
.RE
.SH OUTPUT
The output is a process table from
.BR ps (1)
command.
.RS 4
PID PPID USER NI PSR COMMAND
.RE
Where each of these fields are defined in
.BR ps (1)
manual. For your convenience here's a definition for each field.
.RS 4
.TS
l lw(3i).
\fBCOLUMN DESCRIPTION\fR
PID Process ID
PPID Parent process ID
USER User name owning the process.
NI T{
Nice value. This ranges from 19 (nicest) to \-20 (not nice to others),
See
.IR nice (1).
T}
PSR T{
Processor that process is currently assigned to. Useful when setting process affinity using
.IR taskset (1)
utility.
T}
COMMAND Command name (only the executable name).
.TE
.RE
.SH SEE ALSO
.BR gamemodrun (1),
.BR nice (1),
.BR ps (1),
.BR taskset (1).
.SH ABOUT
GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR
.SH AUTHOR
.BR gamemodelist
was authored by Sam Gleske (https://github.com/samrocketman/)
.BR GameMode
was authored by Feral Interactive (linux-contact@feralinteractive.com)

9
data/gamemoderun Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# Helper script to launch games with gamemode
GAMEMODEAUTO_NAME="libgamemodeauto.so.0"
# ld will find the right path to load the library, including for 32-bit apps.
LD_PRELOAD="${GAMEMODEAUTO_NAME}${LD_PRELOAD:+:$LD_PRELOAD}"
exec env LD_PRELOAD="${LD_PRELOAD}" $GAMEMODERUNEXEC "$@"

50
data/gamemoderun.1.in Normal file
View File

@ -0,0 +1,50 @@
.\" Manpage for gamemoderun.
.\" Contact linux-contact@feralinteractive.com to correct errors or typos.
.TH gamemoderun 1 "4 May 2020" "@GAMEMODE_VERSION@" "gamemoderun man page"
.SH NAME
gamemoderun \- invoke gamemode into any program
.SH SYNOPSIS
\fBgamemoderun\fR PROGRAM
.SH DESCRIPTION
\fBGameMode\fR is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS.
The design has a clear cut abstraction between the host daemon and library (\fBgamemoded\fR and \fBlibgamemode\fR), and the client loaders (\fBlibgamemodeauto\fR and \fBgamemode_client.h\fR) that allows for safe usage without worrying about whether the daemon is installed or running. This design also means that while the host library currently relies on systemd for exchanging messages with the daemon, it's entirely possible to implement other internals that still work with the same clients.
\fBGameMode\fR was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is intended to be expanded beyond just CPU governor states, as there are a wealth of automation tasks one might want to apply.
.SH USAGE
\fBlibgamemodeauto.so.0\fR can be pre-loaded into any program to request \fBgamemoded\fR begin or end the mode, like so:
.RS 4
gamemoderun \./game
.RE
Or by setting the Steam launch options for a game:
.RS 4
gamemoderun %command%
.RE
The library can be manually preloaded if needed:
.RS 4
LD_PRELOAD=$LD_PRELOAD:/usr/\e$LIB/libgamemodeauto.so.0 ./game
.RE
.SH CONFIG
It is possible to set additional start commands to gamemoderun by setting the environment variable:
.RS 4
GAMEMODERUNEXEC="command"
.RE
When this is set, gamemoderun will execute the command given by that environment variable, and the command line passed to gamemoderun will be passed as arguments to that command. GameMode will not be applied to the wrapper command, just the game itself.
.SH SEE ALSO
gamemoded(8)
.SH ABOUT
GameMode source can be found at \fIhttps://github.com/FeralInteractive/gamemode.git\fR
.SH AUTHOR
Feral Interactive (linux-contact@feralinteractive.com)

View File

@ -1,11 +0,0 @@
#!/bin/bash
# Helper script to launch games with gamemode
# Path to install gamemoded auto script
CONFIG_LIB_DIR="@GAMEMODE_LIB_DIR@/libgamemodeauto.so.0"
# Set the ld preload path prefixed libgamemodeauto
export LD_PRELOAD="${CONFIG_LIB_DIR}${LD_PRELOAD:+:$LD_PRELOAD}"
# Launch
exec "$@"

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<component>
<id>io.github.feralinteractive.gamemode</id>
<name>gamemode</name>
<summary>daemon that allows games to request a set of optimizations be temporarily applied</summary>
<developer_name>Feral Interactive</developer_name>
<!-- <icon type="stock">io.github.feralinteractive.gamemode</icon> -->
<metadata_license>FSFAP</metadata_license>
<project_license>BSD-3-Clause</project_license>
<description>
<p>
GameMode is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS and/or a game process.
</p>
<p>
It was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is now host to a range of optimisation features and configurations.
</p>
<p>Currently GameMode includes support for optimisations including:</p>
<ul>
<li>CPU governor</li>
<li>I/O priority</li>
<li>Process niceness</li>
<li>Kernel scheduler (SCHED_ISO)</li>
<li>Screensaver inhibiting</li>
<li>GPU performance mode (NVIDIA and AMD)</li>
<li>GPU overclocking (NVIDIA)</li>
<li>Custom scripts</li>
</ul>
</description>
<content_rating type="oars-1.1" />
<categories>
<category>Utility</category>
<category>Game</category>
</categories>
<url type="homepage">https://feralinteractive.github.io/gamemode</url>
</component>

View File

@ -1,55 +1,157 @@
data_conf = configuration_data()
data_conf.set('BINDIR', path_bindir)
data_conf.set('LIBEXECDIR', path_libexecdir)
data_conf.set('GAMEMODE_LIB_DIR', path_libdir)
data_conf.set('SYSCONFDIR', path_sysconfdir)
data_conf.set('GAMEMODE_PREFIX', path_prefix)
data_conf.set('GAMEMODE_VERSION', meson.project_version())
data_conf.set('GAMEMODE_PRIVILEGED_GROUP', with_privileged_group)
# Pull in the example config
config_example = run_command(
'cat',
join_paths(meson.source_root(), 'example', 'gamemode.ini')
join_paths(meson.project_source_root(), 'example', 'gamemode.ini'),
check: true,
).stdout().strip()
data_conf.set('GAMEMODE_EXAMPLE_CONFIG', config_example)
if with_systemd == true
# Install systemd user unit
configure_file(
input: 'gamemoded.service.in',
output: 'gamemoded.service',
configuration: data_conf,
install_dir: path_systemd_unit_dir,
)
if sd_bus_provider == 'systemd'
if with_systemd_unit
# Install systemd user unit
configure_file(
input: 'systemd/user/gamemoded.service.in',
output: 'gamemoded.service',
configuration: data_conf,
install_dir: path_systemd_unit_dir,
)
endif
if with_systemd_group
# Install the sysusers.d file
configure_file(
input: 'systemd/sysusers.d/gamemode.conf.in',
output: 'gamemode.conf',
configuration: data_conf,
install_dir: path_systemd_group_dir,
)
endif
endif
if with_pam_renicing
# Install the limits.d configuration file
configure_file(
input: 'pam_limits/10-gamemode.conf.in',
output: '10-gamemode.conf',
configuration: data_conf,
install_dir: path_pam_limits_dir,
)
endif
# Install the D-BUS service file
configure_file(
input: 'com.feralinteractive.GameMode.service.in',
input: 'dbus/com.feralinteractive.GameMode.service.in',
output: 'com.feralinteractive.GameMode.service',
configuration: data_conf,
install_dir: path_dbus_service_dir,
)
# Install the Polkit action file in all cases
configure_file(
input: 'com.feralinteractive.GameMode.policy.in',
output: 'com.feralinteractive.GameMode.policy',
configuration: data_conf,
install_dir: path_polkit_action_dir,
)
# Install the Polkit action & rule files for the privileged gamemode group
if with_privileged_group != ''
configure_file(
input: 'polkit/actions/com.feralinteractive.GameMode.policy.in',
output: 'com.feralinteractive.GameMode.policy',
configuration: data_conf,
install_dir: path_polkit_action_dir,
)
configure_file(
input: 'polkit/rules.d/gamemode.rules.in',
output: 'gamemode.rules',
configuration: data_conf,
install_dir: path_polkit_rule_dir,
)
endif
# Install the helper run script
configure_file(
input: 'gamemoderun.in',
output: 'gamemoderun',
configuration: data_conf,
install_dir: 'bin',
# Install the helper run script and man page
if get_option('default_library') == 'static'
warning('gamemoderun will not be installed as a shared libgamemodeauto library is required')
else
install_data(
files('gamemoderun'),
install_dir: path_bindir,
install_mode: 'rwxr-xr-x',
)
gamemoderun_manpage = configure_file(
input: files('gamemoderun.1.in'),
output: 'gamemoderun.1',
configuration: data_conf,
)
install_man(
gamemoderun_manpage,
install_dir: join_paths(path_mandir, 'man1')
)
endif
# Install script to find processes with gamemode lib in runtime
install_data(
files('gamemodelist'),
install_dir: path_bindir,
install_mode: 'rwxr-xr-x',
)
# Configure and install the man page
manpage = configure_file(
# Configure and install man pages
gamemoded_manpage = configure_file(
input: files('gamemoded.8.in'),
output: 'gamemoded.8',
configuration: data_conf,
)
install_man(manpage)
install_man(
gamemoded_manpage,
install_dir: join_paths(path_mandir, 'man8')
)
gamemodelist_manpage = configure_file(
input: files('gamemodelist.1.in'),
output: 'gamemodelist.1',
configuration: data_conf,
)
install_man(
gamemodelist_manpage,
install_dir: join_paths(path_mandir, 'man1')
)
if with_examples
example_manpage = configure_file(
input: files('gamemode-simulate-game.1.in'),
output: 'gamemode-simulate-game.1',
configuration: data_conf,
)
install_man(
example_manpage,
install_dir: join_paths(path_mandir, 'man1')
)
endif
# Install metainfo
metainfo_file = files('io.github.feralinteractive.gamemode.metainfo.xml')
install_data(
metainfo_file,
install_dir: path_metainfo,
)
# Validate metainfo
appstreamcli = find_program(
'appstreamcli',
required: false
)
if appstreamcli.found()
test(
'validate metainfo file',
appstreamcli,
args: ['validate', '--no-net', '--pedantic', metainfo_file],
)
endif

View File

@ -0,0 +1 @@
@@GAMEMODE_PRIVILEGED_GROUP@ - nice -10

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
<policyconfig>
<!--
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
-->
<vendor>Feral GameMode Activation</vendor>
<vendor_url>http://www.feralinteractive.com</vendor_url>
<action id="com.feralinteractive.GameMode.governor-helper">
<description>Modify the CPU governor</description>
<message>Authentication is required to modify the CPU governor</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>no</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/cpugovctl</annotate>
</action>
<action id="com.feralinteractive.GameMode.gpu-helper">
<description>Modify the GPU clock states</description>
<message>Authentication is required to modify the GPU clock states</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>no</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/gpuclockctl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
<action id="com.feralinteractive.GameMode.cpu-helper">
<description>Modify the CPU core states</description>
<message>Authentication is required to modify the CPU core states</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>no</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/cpucorectl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
<action id="com.feralinteractive.GameMode.procsys-helper">
<description>Modify the /proc/sys values</description>
<message>Authentication is required to modify the /proc/sys/ values</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>no</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/procsysctl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
<action id="com.feralinteractive.GameMode.profile-helper">
<description>Modify the platform profile</description>
<message>Authentication is required to modify platform profile</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>no</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">@LIBEXECDIR@/platprofctl</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
</action>
</policyconfig>

View File

@ -0,0 +1,15 @@
/*
* Allow users in privileged gamemode group to run cpugovctl &
* gpuclockctl without authentication
*/
polkit.addRule(function (action, subject) {
if ((action.id == "com.feralinteractive.GameMode.governor-helper" ||
action.id == "com.feralinteractive.GameMode.gpu-helper" ||
action.id == "com.feralinteractive.GameMode.cpu-helper" ||
action.id == "com.feralinteractive.GameMode.procsys-helper" ||
action.id == "com.feralinteractive.GameMode.profile-helper") &&
subject.isInGroup("@GAMEMODE_PRIVILEGED_GROUP@"))
{
return polkit.Result.YES;
}
});

View File

@ -0,0 +1 @@
g @GAMEMODE_PRIVILEGED_GROUP@ - -

View File

@ -5,7 +5,7 @@ Description=gamemoded
Type=dbus
BusName=com.feralinteractive.GameMode
NotifyAccess=main
ExecStart=@BINDIR@/gamemoded -l
ExecStart=@BINDIR@/gamemoded
[Install]
WantedBy=default.target

View File

@ -1,30 +0,0 @@
# Maintainer: Ysblokje <ysblokje at gmail dot com>
pkgname=('gamemode-git')
pkgver='1.3-dev'
pkgrel=1
pkgdesc="GameMode is a daemon/lib combo for Linux that allows games to request a set of optimisations be temporarily applied to the host OS."
arch=('x86_64')
url="https://github.com/FeralInteractive/gamemode.git"
license=('MIT')
depends=('systemd' 'polkit')
makedepends=('meson' 'pkg-config')
provides=('gamemode')
source=("git+https://github.com/FeralInteractive/gamemode.git")
sha256sums=('SKIP')
pkgver() {
cd gamemode
echo $(git rev-parse --short HEAD)
}
build() {
cd gamemode
arch-meson build
cd build
ninja
}
package() {
cd gamemode/build
DESTDIR=$pkgdir ninja install
}

View File

@ -1,4 +0,0 @@
The following folders contain PKGBUILD file for arch(like) distro's. You can use those as starting point for your own packages.
Regards,
Minze Zwerver

View File

@ -1,11 +1,25 @@
[general]
; The reaper thread will check every 5 seconds for exited clients
; The reaper thread will check every 5 seconds for exited clients, for config file changes, and for the CPU/iGPU power balance
reaper_freq=5
; The desired governor is used when entering GameMode instead of "performance"
desiredgov=performance
; The default governer is used when leaving GameMode instead of restoring the original value
defaultgov=powersave
; The default governor is used when leaving GameMode instead of restoring the original value
;defaultgov=powersave
; The desired platform profile is used when entering GameMode instead of "performance"
desiredprof=performance
; The default platform profile is used when leaving GameMode instead of restoring the original value
;defaultgov=low-power
; The iGPU desired governor is used when the integrated GPU is under heavy load
igpu_desiredgov=powersave
; Threshold to use to decide when the integrated GPU is under heavy load.
; This is a ratio of iGPU Watts / CPU Watts which is used to determine when the
; integraged GPU is under heavy enough load to justify switching to
; igpu_desiredgov. Set this to -1 to disable all iGPU checking and always
; use desiredgov for games.
igpu_power_threshold=0.3
; GameMode can change the scheduler policy to SCHED_ISO on kernels which support it (currently
; not supported by upstream kernels). Can be set to "auto", "on" or "off". "auto" will enable
@ -14,6 +28,8 @@ softrealtime=off
; GameMode can renice game processes. You can put any value between 0 and 20 here, the value
; will be negated and applied as a nice value (0 means no change). Defaults to 0.
; To use this feature, the user must be added to the gamemode group (and then rebooted):
; sudo usermod -aG gamemode $(whoami)
renice=0
; By default, GameMode adjusts the iopriority of clients to BE/0, you can put any value
@ -26,6 +42,10 @@ ioprio=0
; Defaults to 1
inhibit_screensaver=1
; Sets whether gamemode will disable split lock mitigation when active
; Defaults to 1
disable_splitlock=1
[filter]
; If "whitelist" entry has a value(s)
; gamemode will reject anything not in the whitelist
@ -66,6 +86,17 @@ inhibit_screensaver=1
; This corresponds to power_dpm_force_performance_level, "manual" is not supported for now
;amd_performance_level=high
[cpu]
; Parking or Pinning can be enabled with either "yes", "true" or "1" and disabled with "no", "false" or "0".
; Either can also be set to a specific list of cores to park or pin, comma separated list where "-" denotes
; a range. E.g "park_cores=1,8-15" would park cores 1 and 8 to 15.
; The default is uncommented is to disable parking but enable pinning. If either is enabled the code will
; currently only properly autodetect Ryzen 7900x3d, 7950x3d and Intel CPU:s with E- and P-cores.
; For Core Parking, user must be added to the gamemode group (not required for Core Pinning):
; sudo usermod -aG gamemode $(whoami)
;park_cores=no
;pin_cores=yes
[supervisor]
; This section controls the new gamemode functions gamemode_request_start_for and gamemode_request_end_for
; The whilelist and blacklist control which supervisor programs are allowed to make the above requests
@ -79,10 +110,10 @@ inhibit_screensaver=1
[custom]
; Custom scripts (executed using the shell) when gamemode starts and ends
;start=notify-send "GameMode started"
; /home/me/bin/stop_ethmining.sh
; /home/me/bin/stop_foldingathome.sh
;end=notify-send "GameMode ended"
; /home/me/bin/start_ethmining.sh
; /home/me/bin/start_foldingathome.sh
; Timeout for scripts (seconds). Scripts will be killed if they do not complete within this time.
;script_timeout=10

View File

@ -1,5 +0,0 @@
[Desktop Entry]
Name=gamemoded
Exec=systemctl --user restart gamemoded.service
Type=Application
Terminal=false

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -32,6 +32,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "gamemode_client.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
@ -39,6 +40,7 @@ int main(void)
/* Request we start game mode */
if (gamemode_request_start() != 0) {
fprintf(stderr, "Failed to request gamemode start: %s...\n", gamemode_error_string());
return EXIT_FAILURE;
}
/* Simulate running a game */
@ -47,5 +49,8 @@ int main(void)
/* Request we end game mode (optional) */
if (gamemode_request_end() != 0) {
fprintf(stderr, "Failed to request gamemode end: %s...\n", gamemode_error_string());
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -1,11 +1,18 @@
# An example game
executable(
'example',
'gamemode-simulate-game',
sources: [
'main.c',
],
include_directories: libgamemode_includes,
dependencies: [
libdl,
gamemode_dep,
],
install: true,
install_dir: path_bindir,
)
# An example configuration
install_data(
files('gamemode.ini'),
install_dir: path_sysconfdir,
)

43
lib/README.md Normal file
View File

@ -0,0 +1,43 @@
## libgamemode
**libgamemode** is an internal library used to dispatch requests to the daemon. Note: `libgamemode` should never be linked with directly.
**libgamemodeauto** is a simple dynamic library that automatically requests game mode when loaded. Useful to `LD_PRELOAD` into any game as needed.
**gamemode\_client.h** is as single header lib that lets a game request game mode and handle errors.
### Integration
Developers can integrate the request directly into an app. Note that none of these client methods force your users to have the daemon installed or running - they will safely no-op if the host is missing.
```C
// Manually with error checking
#include "gamemode_client.h"
if( gamemode_request_start() < 0 ) {
fprintf( stderr, "gamemode request failed: %s\n", gamemode_error_string() );
}
/* run game... */
gamemode_request_end(); // Not required, gamemoded can clean up after game exits
```
```C
// Automatically on program start and finish
#define GAMEMODE_AUTO
#include "gamemode_client.h"
```
Or, distribute `libgamemodeauto.so` and either add `-lgamemodeauto` to your linker arguments, or add it to an LD\_PRELOAD in a launch script.
### Supervisor support
Developers can also create apps that manage GameMode on the system, for other processes:
```C
#include "gamemode_client.h"
gamemode_request_start_for(gamePID);
gamemode_request_end_for(gamePID);
```
This functionality can also be controlled in the config file in the `supervisor` section.

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,70 +31,329 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <common-helpers.h>
#include <common-pidfds.h>
#include <dbus/dbus.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <systemd/sd-bus.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
// For developmental purposes
#define DO_TRACE 0
// D-Bus name, path, iface
#define DAEMON_DBUS_NAME "com.feralinteractive.GameMode"
#define DAEMON_DBUS_PATH "/com/feralinteractive/GameMode"
#define DAEMON_DBUS_IFACE "com.feralinteractive.GameMode"
#define PORTAL_DBUS_NAME "org.freedesktop.portal.Desktop"
#define PORTAL_DBUS_PATH "/org/freedesktop/portal/desktop"
#define PORTAL_DBUS_IFACE "org.freedesktop.portal.GameMode"
// Cleanup macros
#define _cleanup_(x) __attribute__((cleanup(x)))
#define _cleanup_bus_ _cleanup_(hop_off_the_bus)
#define _cleanup_msg_ _cleanup_(cleanup_msg)
#define _cleanup_dpc_ _cleanup_(cleanup_pending_call)
#define _cleanup_fds_ _cleanup_(cleanup_fd_array)
#ifdef NDEBUG
#define DEBUG(...)
#else
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#endif
#if DO_TRACE
#define TRACE(...) fprintf(stderr, __VA_ARGS__)
#else
#define TRACE(...)
#endif
// Prototypes
static int log_error(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
// Storage for error strings
static char error_string[512] = { 0 };
// Simple requestor function for a gamemode
static int gamemode_request(const char *function, int arg)
// memory helpers
static void cleanup_fd_array(int **fdlist)
{
sd_bus_message *msg = NULL;
sd_bus *bus = NULL;
sd_bus_error err;
memset(&err, 0, sizeof(err));
if (fdlist == NULL || *fdlist == NULL)
return;
int result = -1;
int errsave = errno;
for (int *fd = *fdlist; *fd != -1; fd++) {
TRACE("GM Closing fd %d\n", *fd);
(void)close(*fd);
}
// Open the user bus
int ret = sd_bus_open_user(&bus);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
"Could not connect to bus: %s",
strerror(-ret));
} else {
// Attempt to send the requested function
ret = sd_bus_call_method(bus,
"com.feralinteractive.GameMode",
"/com/feralinteractive/GameMode",
"com.feralinteractive.GameMode",
function,
&err,
&msg,
arg ? "ii" : "i",
getpid(),
arg);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
"Could not call method %s on com.feralinteractive.GameMode\n"
"\t%s\n"
"\t%s\n"
"\t%s\n",
function,
err.name,
err.message,
strerror(-ret));
} else {
// Read the reply
ret = sd_bus_message_read(msg, "i", &result);
if (ret < 0) {
snprintf(error_string,
sizeof(error_string),
"Failure to parse response: %s",
strerror(-ret));
}
errno = errsave;
free(*fdlist);
}
// Allocate a -1 termianted array of ints
static inline int *alloc_fd_array(int n)
{
int *fds;
size_t count = (size_t)n + 1; /* -1, terminated */
fds = (int *)malloc(sizeof(int) * count);
for (size_t i = 0; i < count; i++)
fds[i] = -1;
return fds;
}
// Helper to check if we are running inside a sandboxed framework like Flatpak or Snap
static int in_sandbox(void)
{
static int status = -1;
if (status == -1) {
struct stat sb;
int r;
r = lstat("/.flatpak-info", &sb);
status = r == 0 && sb.st_size > 0;
if (getenv("SNAP") != NULL) {
status = 1;
}
}
return result;
return status;
}
static int log_error(const char *fmt, ...)
{
va_list args;
int n;
va_start(args, fmt);
n = vsnprintf(error_string, sizeof(error_string), fmt, args);
va_end(args);
if (n < 0)
DEBUG("Failed to format error string");
else if ((size_t)n >= sizeof(error_string))
DEBUG("Error log overflow");
fprintf(stderr, "GameMode ERROR: %s\n", error_string);
return -1;
}
static void hop_off_the_bus(DBusConnection **bus)
{
if (bus == NULL || *bus == NULL)
return;
dbus_connection_unref(*bus);
}
static DBusConnection *hop_on_the_bus(void)
{
DBusConnection *bus;
DBusError err;
dbus_error_init(&err);
bus = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (bus == NULL) {
log_error("Could not connect to bus: %s", err.message);
dbus_error_free(&err);
}
return bus;
}
/* cleanup functions */
static void cleanup_msg(DBusMessage **msg)
{
if (msg == NULL || *msg == NULL)
return;
dbus_message_unref(*msg);
}
static void cleanup_pending_call(DBusPendingCall **call)
{
if (call == NULL || *call == NULL)
return;
dbus_pending_call_unref(*call);
}
/* internal API */
static int make_request(DBusConnection *bus, int native, int use_pidfds, const char *method,
pid_t *pids, int npids, DBusError *error)
{
_cleanup_msg_ DBusMessage *msg = NULL;
_cleanup_dpc_ DBusPendingCall *call = NULL;
_cleanup_fds_ int *fds = NULL;
char action[256] = {
0,
};
DBusError err;
DBusMessageIter iter;
int res = -1;
TRACE("GM: Incoming request: %s, npids: %d, native: %d pifds: %d\n",
method,
npids,
native,
use_pidfds);
if (use_pidfds) {
fds = alloc_fd_array(npids);
res = open_pidfds(pids, fds, npids);
if (res != npids) {
dbus_set_error(error, DBUS_ERROR_FAILED, "Could not open pidfd for %d", (int)pids[res]);
return -1;
}
if (strstr(method, "ByPID"))
snprintf(action, sizeof(action), "%sFd", method);
else
snprintf(action, sizeof(action), "%sByPIDFd", method);
method = action;
}
TRACE("GM: Making request: %s, npids: %d, native: %d pifds: %d\n",
method,
npids,
native,
use_pidfds);
// If we are inside a Flatpak or Snap we need to talk to the portal instead
const char *dest = native ? DAEMON_DBUS_NAME : PORTAL_DBUS_NAME;
const char *path = native ? DAEMON_DBUS_PATH : PORTAL_DBUS_PATH;
const char *iface = native ? DAEMON_DBUS_IFACE : PORTAL_DBUS_IFACE;
msg = dbus_message_new_method_call(dest, path, iface, method);
if (!msg) {
dbus_set_error_const(error, DBUS_ERROR_FAILED, "Could not create dbus message");
return -1;
}
dbus_message_iter_init_append(msg, &iter);
for (int i = 0; i < npids; i++) {
dbus_int32_t p;
int type;
if (use_pidfds) {
type = DBUS_TYPE_UNIX_FD;
p = (dbus_int32_t)fds[i];
} else {
type = DBUS_TYPE_INT32;
p = (dbus_int32_t)pids[i];
}
dbus_message_iter_append_basic(&iter, type, &p);
}
dbus_connection_send_with_reply(bus, msg, &call, -1);
dbus_connection_flush(bus);
dbus_message_unref(msg);
msg = NULL;
dbus_pending_call_block(call);
msg = dbus_pending_call_steal_reply(call);
if (msg == NULL) {
dbus_set_error_const(error, DBUS_ERROR_FAILED, "Did not receive a reply");
return -1;
}
dbus_error_init(&err);
res = -1;
if (dbus_set_error_from_message(&err, msg)) {
dbus_set_error(error,
err.name,
"Could not call method '%s' on '%s': %s",
method,
dest,
err.message);
} else if (!dbus_message_iter_init(msg, &iter) ||
dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INT32) {
dbus_set_error(error, DBUS_ERROR_INVALID_SIGNATURE, "Failed to parse response");
} else {
dbus_message_iter_get_basic(&iter, &res);
}
/* free the local error */
if (dbus_error_is_set(&err))
dbus_error_free(&err);
return res;
}
static int gamemode_request(const char *method, pid_t for_pid)
{
_cleanup_bus_ DBusConnection *bus = NULL;
static int use_pidfs = 1;
DBusError err;
pid_t pids[2];
int npids;
int native;
int res = -1;
native = !in_sandbox();
/* pid[0] is the client, i.e. the game
* pid[1] is the requestor, i.e. this process
*
* we setup the array such that pids[1] will always be a valid
* pid, because if we are going to use the pidfd based API,
* both pids are being sent, even if they are the same
*/
pids[1] = getpid();
pids[0] = for_pid != 0 ? for_pid : pids[1];
TRACE("GM: [%d] request '%s' received (by: %d) [portal: %s]\n",
(int)pids[0],
method,
(int)pids[1],
(native ? "n" : "y"));
bus = hop_on_the_bus();
if (bus == NULL)
return -1;
dbus_error_init(&err);
retry:
if (for_pid != 0 || use_pidfs)
npids = 2;
else
npids = 1;
res = make_request(bus, native, use_pidfs, method, pids, npids, &err);
if (res == -1 && use_pidfs && dbus_error_is_set(&err)) {
TRACE("GM: Request with pidfds failed (%s). Retrying.\n", err.message);
use_pidfs = 0;
dbus_error_free(&err);
goto retry;
}
if (res == -1 && dbus_error_is_set(&err))
log_error("D-Bus error: %s", err.message);
TRACE("GM: [%d] request '%s' done: %d\n", (int)pids[0], method, res);
if (dbus_error_is_set(&err))
dbus_error_free(&err);
return res;
}
// Get the error string

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -42,6 +42,9 @@ POSSIBILITY OF SUCH DAMAGE.
* 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
@ -58,23 +61,27 @@ POSSIBILITY OF SUCH DAMAGE.
* -1 if the request failed
* -2 if the request was rejected
*
* int gamemode_query_status_for(pid_t pid) - Query the current 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
* 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 <stdlib.h>
#include <unistd.h>
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
static char internal_gamemode_client_error_string[512] = { 0 };
@ -226,11 +233,14 @@ __attribute__((always_inline)) static inline const char *gamemode_error_string(v
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 libgamemod
* Redirect to the real libgamemode
* Allow automatically requesting game mode
* Also prints errors as they happen.
*/
@ -249,6 +259,9 @@ int gamemode_request_start(void)
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());
@ -275,6 +288,9 @@ int gamemode_request_end(void)
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());

View File

@ -6,25 +6,26 @@ lt_age = '0'
lt_version = '@0@.@1@.@2@'.format(lt_current, lt_age, lt_revision)
# Main client library to message the daemon
gamemode = shared_library(
libgamemode = shared_library(
'gamemode',
sources: [
'client_impl.c',
],
dependencies: [
dep_systemd,
link_lib_common,
dep_dbus,
],
install: true,
soversion: lt_current,
version: lt_version,
)
libgamemode_includes = [
gamemode_headers_includes = [
include_directories('.'),
]
# Small library to automatically use gamemode
gamemodeauto = shared_library(
libgamemodeauto = library(
'gamemodeauto',
sources: [
'client_loader.c',
@ -42,7 +43,10 @@ gamemode_headers = [
'gamemode_client.h',
]
install_headers(gamemode_headers)
install_headers(
gamemode_headers,
install_dir: path_includedir,
)
# Generate a pkg-config files
pkg = import('pkgconfig')
@ -58,13 +62,18 @@ pkg.generate(
)
pkg.generate(
name: 'gamemode',
name: 'libgamemodeauto',
description: desc,
filebase: 'gamemode-auto',
libraries: gamemodeauto,
filebase: 'libgamemodeauto',
libraries: libgamemodeauto,
version: meson.project_version(),
libraries_private: [
libdl
],
)
# Dependency objects
gamemode_dep = declare_dependency(
include_directories: gamemode_headers_includes,
dependencies: libdl,
)
libgamemodeauto_dep = declare_dependency(
link_with: libgamemodeauto,
)

View File

@ -2,8 +2,9 @@ project(
'gamemode',
'c',
default_options : ['c_std=c11', 'warning_level=3'],
version: '1.3',
version: '1.8.2',
license: 'BSD',
meson_version: '>= 1.3.1',
)
am_cflags = [
@ -23,15 +24,78 @@ add_global_arguments(am_cflags, language: 'c')
cc = meson.get_compiler('c')
# additional compiler warnings, if supported
test_args = [
'-Waggregate-return',
'-Wunused',
'-Warray-bounds',
'-Wcast-align',
'-Wclobbered',
'-Wempty-body',
'-Wformat=2',
'-Wformat-nonliteral',
'-Wformat-signedness',
'-Wignored-qualifiers',
'-Wimplicit-function-declaration',
'-Winit-self',
'-Wmissing-format-attribute',
'-Wmissing-include-dirs',
'-Wmissing-noreturn',
'-Wmissing-parameter-type',
'-Wnested-externs',
'-Wno-discarded-qualifiers',
'-Wno-missing-field-initializers',
'-Wno-suggest-attribute=format',
'-Wno-unused-parameter',
'-Wold-style-definition',
'-Woverride-init',
'-Wpointer-arith',
'-Wredundant-decls',
'-Wreturn-type',
'-Wshadow',
'-Wsign-compare',
'-Wstrict-aliasing=3',
'-Wstrict-prototypes',
'-Wstringop-overflow',
'-Wstringop-truncation',
'-Wtype-limits',
'-Wundef',
'-Wuninitialized',
'-Wunused-but-set-variable',
'-Wwrite-strings',
]
foreach arg: test_args
if cc.has_argument(arg)
add_global_arguments(arg, language : 'c')
endif
endforeach
path_prefix = get_option('prefix')
path_bindir = join_paths(path_prefix, get_option('bindir'))
path_datadir = join_paths(path_prefix, get_option('datadir'))
path_includedir = join_paths(path_prefix, get_option('includedir'))
path_libdir = join_paths(path_prefix, get_option('libdir'))
path_libexecdir = join_paths(path_prefix, get_option('libexecdir'))
path_mandir = join_paths(path_prefix, get_option('mandir'))
path_metainfo = join_paths(path_datadir, 'metainfo')
path_sysconfdir = join_paths(path_datadir, 'gamemode')
# Find systemd via pkgconfig
dep_systemd = dependency('libsystemd')
# Find systemd / elogind via pkgconfig
sd_bus_provider = get_option('with-sd-bus-provider')
sd_bus_args = []
sd_bus_dep = []
if sd_bus_provider == 'systemd'
sd_bus_dep = dependency('libsystemd')
elif sd_bus_provider == 'elogind'
sd_bus_args += ['-DUSE_ELOGIND']
sd_bus_dep = dependency('libelogind')
endif
# For the client, libdbus is used
dep_dbus = dependency('dbus-1')
# Allow meson to figure out how the compiler sets up threading
dep_threads = dependency('threads')
@ -39,28 +103,41 @@ dep_threads = dependency('threads')
# On non glibc systems this might be a stub, i.e. for musl
libdl = cc.find_library('dl', required: false)
with_systemd = get_option('with-systemd')
if with_systemd == true
# If the path isn't explicitly set, ask systemd for the systemd user unit directory
path_systemd_unit_dir = get_option('with-systemd-user-unit-dir')
if path_systemd_unit_dir == ''
message('Asking pkg-config for systemd\'s directories')
pkgconfig_systemd = dependency('systemd')
path_systemd_unit_dir = pkgconfig_systemd.get_pkgconfig_variable('systemduserunitdir')
with_privileged_group = get_option('with-privileged-group')
# Determine the location for the systemd unit
if sd_bus_provider == 'systemd'
with_systemd_unit = get_option('with-systemd-user-unit')
if with_systemd_unit
path_systemd_unit_dir = get_option('with-systemd-user-unit-dir')
if path_systemd_unit_dir == ''
message('Asking pkg-config for systemd\'s \'systemduserunitdir\' directory')
pkgconfig_systemd = dependency('systemd')
path_systemd_unit_dir = pkgconfig_systemd.get_variable(pkgconfig: 'systemduserunitdir')
endif
endif
if with_privileged_group != ''
with_systemd_group = get_option('with-systemd-group')
if with_systemd_group
path_systemd_group_dir = get_option('with-systemd-group-dir')
if path_systemd_group_dir == ''
message('Asking pkg-config for systemd\'s \'sysusersdir\' directory')
pkgconfig_systemd = dependency('systemd')
path_systemd_group_dir = pkgconfig_systemd.get_variable(pkgconfig: 'sysusersdir')
endif
endif
else
with_systemd_group = false
endif
endif
with_limits_conf = get_option('with-pam-group')
if with_limits_conf != ''
ldata = configuration_data()
ldata.set('LIMITSGROUP', with_limits_conf)
# Install the limits.d configuration file
configure_file(
input: 'data/10-gamemode.conf.in',
output: '10-gamemode.conf',
configuration: ldata,
install_dir: '/etc/security/limits.d',
)
if with_privileged_group != ''
with_pam_renicing = get_option('with-pam-renicing')
if with_pam_renicing
path_pam_limits_dir = get_option('with-pam-limits-dir')
endif
else
with_pam_renicing = false
endif
# Set the dbus path as appropriate.
@ -69,30 +146,48 @@ if path_dbus_service_dir == ''
path_dbus_service_dir = join_paths(path_datadir, 'dbus-1', 'services')
endif
path_polkit_action_dir = join_paths(path_datadir, 'polkit-1', 'actions')
path_polkit_dir = join_paths(path_datadir, 'polkit-1')
path_polkit_action_dir = join_paths(path_polkit_dir, 'actions')
path_polkit_rule_dir = join_paths(path_polkit_dir, 'rules.d')
with_daemon = get_option('with-daemon')
with_examples = get_option('with-examples')
with_util = get_option('with-util')
# Provide a config.h
pidfd_open = cc.has_function('pidfd_open', args: '-D_GNU_SOURCE')
cdata = configuration_data()
cdata.set_quoted('LIBEXECDIR', path_libexecdir)
cdata.set_quoted('SYSCONFDIR', path_sysconfdir)
cdata.set_quoted('GAMEMODE_VERSION', meson.project_version())
cdata.set10('HAVE_FN_PIDFD_OPEN', pidfd_open)
config_h = configure_file(
configuration: cdata,
output: 'config.h',
output: 'build-config.h',
)
config_h_dir = include_directories('.')
# common lib is always required
subdir('common')
# Library is always required
subdir('lib')
# Utilities are always required except when having both 64 and 32 bit versions
# of libgamemode installed
if with_util == true
subdir('util')
endif
# The daemon can be disabled if necessary, allowing multilib builds of the
# main library
if with_daemon == true
if sd_bus_provider != 'no-daemon'
# inih currently only needed by the daemon
inih = subproject('inih')
inih_dependency = inih.get_variable('inih_dependency')
inih_dependency = dependency(
'inih',
fallback : ['inih', 'inih_dep']
)
subdir('daemon')
@ -118,11 +213,23 @@ report = [
' includedir: @0@'.format(path_includedir),
]
if with_systemd == true
if with_pam_renicing
report += [
' PAM limits.d directory: @0@'.format(path_pam_limits_dir),
]
endif
if sd_bus_provider == 'systemd'
if with_systemd_unit
report += [
' systemd user unit directory: @0@'.format(path_systemd_unit_dir),
]
endif
if with_systemd_group
report += [
' systemd group directory: @0@'.format(path_systemd_group_dir),
]
endif
endif
report += [
' D-BUS service directory: @0@'.format(path_dbus_service_dir),
]
@ -134,9 +241,9 @@ report += [
' Options:',
' ========',
'',
' daemon: @0@'.format(with_daemon),
' sd-bus provier: @0@'.format(sd_bus_provider),
' examples: @0@'.format(with_examples),
' systemd: @0@'.format(with_systemd),
' util: @0@'.format(with_util),
]
# Output some stuff to validate the build config

View File

@ -1,14 +1,20 @@
option('with-systemd', type: 'boolean', description: 'Use systemd support (unit, etc)', value: 'true')
# limits.d
option('with-pam-group', type: 'string', description: 'Install the limits.d configuration file to allow renicing as an unpriviledged user being part of the specified group')
option('with-pam-renicing', type: 'boolean', description: 'Install the limits.d configuration file to allow renicing as a user being part of the privileged gamemode group', value: true)
option('with-pam-limits-dir', type: 'string', description: 'Explicitly set the PAM limits.d directory', value: '/etc/security/limits.d')
# sd-bus provider
option('with-sd-bus-provider', type: 'combo', choices: ['systemd', 'elogind', 'no-daemon'], value: 'systemd')
# systemd specific
option('with-systemd-user-unit', type: 'boolean', description: 'Install systemd user unit', value: true)
option('with-systemd-user-unit-dir', type: 'string', description: 'Explicitly set the systemd user unit directory')
option('with-systemd-group', type: 'boolean', description: 'Install privileged gamemode group with systemd', value: true)
option('with-systemd-group-dir', type: 'string', description: 'Explicitly set the systemd group directory')
# Not using systemd
option('with-dbus-service-dir', type: 'string', description: 'Explicitly set the D-BUS session directory')
# General options
option('with-examples', type: 'boolean', description: 'Build sample programs', value: 'true')
option('with-daemon', type: 'boolean', description: 'Build the daemon', value: 'true')
option('with-examples', type: 'boolean', description: 'Build sample programs', value: true)
option('with-util', type: 'boolean', description: 'Build the utilities', value: true)
option('with-privileged-group', type: 'string', description: 'Group that has access to privileged gamemode features', value: 'gamemode')

View File

@ -4,24 +4,33 @@
# Ensure we are at the project root
cd "$(dirname $0)"/..
wget -Nq -T3 -t1 https://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/git-clang-format
if [[ "$1" == "--pre-commit" ]]; then
# used via .git/hooks/pre-commit:
# exec "$(dirname $0)"/../../scripts/format-check.sh --pre-commit
git-clang-format
exit
fi
if chmod +x git-clang-format; then
if [[ "$1" == "--pre-commit" ]]; then
# used via .git/hooks/pre-commit:
# exec "$(dirname $0)"/../../scripts/format-check.sh --pre-commit
./git-clang-format
exit
fi
CLANG_FORMAT_OUTPUT=$(./git-clang-format HEAD^ HEAD --diff)
if [[ ! ${CLANG_FORMAT_OUTPUT} == "no modified files to format" ]] && [[ ! -z ${CLANG_FORMAT_OUTPUT} ]]; then
if [[ "$CI" == "true" ]]; then
# used in ci, assumes clean repo
clang-format -i $(find . -name '*.[ch]' -not -path "*subprojects/*")
GIT_DIFF_OUTPUT=$(git diff)
if [[ ! -z ${GIT_DIFF_OUTPUT} ]]; then
echo "Failed clang format check:"
echo "${CLANG_FORMAT_OUTPUT}"
echo "${GIT_DIFF_OUTPUT}"
exit 1
else
echo "Passed clang format check"
exit 0
fi
else
echo "git-clang-format not downloaded"
exit 1
fi
CLANG_FORMAT_OUTPUT=$(git-clang-format HEAD^ HEAD --diff)
if [[ ! ${CLANG_FORMAT_OUTPUT} == "no modified files to format" ]] && [[ ! -z ${CLANG_FORMAT_OUTPUT} ]]; then
echo "Failed clang format check:"
echo "${CLANG_FORMAT_OUTPUT}"
exit 1
else
echo "Passed clang format check"
exit 0
fi

View File

@ -1,313 +0,0 @@
#!/bin/bash -
#
# File: git-archive-all.sh
#
# Description: A utility script that builds an archive file(s) of all
# git repositories and submodules in the current path.
# Useful for creating a single tarfile of a git super-
# project that contains other submodules.
#
# Examples: Use git-archive-all.sh to create archive distributions
# from git repositories. To use, simply do:
#
# cd $GIT_DIR; git-archive-all.sh
#
# where $GIT_DIR is the root of your git superproject.
#
# License: GPL3+
#
###############################################################################
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
###############################################################################
# DEBUGGING
set -e
set -C # noclobber
# TRAP SIGNALS
trap 'cleanup' QUIT EXIT
# For security reasons, explicitly set the internal field separator
# to newline, space, tab
OLD_IFS=$IFS
IFS="$(printf '\n \t')"
function cleanup () {
rm -f $TMPFILE
rm -f $TMPLIST
rm -f $TOARCHIVE
IFS="$OLD_IFS"
}
function usage () {
echo "Usage is as follows:"
echo
echo "$PROGRAM <--version>"
echo " Prints the program version number on a line by itself and exits."
echo
echo "$PROGRAM <--usage|--help|-?>"
echo " Prints this usage output and exits."
echo
echo "$PROGRAM [--format <fmt>] [--prefix <path>] [--verbose|-v] [--separate|-s]"
echo " [--worktree-attributes] [--tree-ish|-t <tree-ish>] [output_file]"
echo " Creates an archive for the entire git superproject, and its submodules"
echo " using the passed parameters, described below."
echo
echo " If '--format' is specified, the archive is created with the named"
echo " git archiver backend. Obviously, this must be a backend that git archive"
echo " understands. The format defaults to 'tar' if not specified."
echo
echo " If '--prefix' is specified, the archive's superproject and all submodules"
echo " are created with the <path> prefix named. The default is to not use one."
echo
echo " If '--worktree-attributes' is specified, the invidual archive commands will"
echo " look for attributes in .gitattributes in the working directory too."
echo
echo " If '--separate' or '-s' is specified, individual archives will be created"
echo " for each of the superproject itself and its submodules. The default is to"
echo " concatenate individual archives into one larger archive."
echo
echo " If '--tree-ish' is specified, the archive will be created based on whatever"
echo " you define the tree-ish to be. Branch names, commit hash, etc. are acceptable."
echo " Defaults to HEAD if not specified. See git archive's documentation for more"
echo " information on what a tree-ish is."
echo
echo " If 'output_file' is specified, the resulting archive is created as the"
echo " file named. This parameter is essentially a path that must be writeable."
echo " When combined with '--separate' ('-s') this path must refer to a directory."
echo " Without this parameter or when combined with '--separate' the resulting"
echo " archive(s) are named with a dot-separated path of the archived directory and"
echo " a file extension equal to their format (e.g., 'superdir.submodule1dir.tar')."
echo
echo " The special value '-' (single dash) is treated as STDOUT and, when used, the"
echo " --separate option is ignored. Use a double-dash to separate the outfile from"
echo " the value of previous options. For example, to write a .zip file to STDOUT:"
echo
echo " ./$PROGRAM --format zip -- -"
echo
echo " If '--verbose' or '-v' is specified, progress will be printed."
}
function version () {
echo "$PROGRAM version $VERSION"
}
# Internal variables and initializations.
readonly PROGRAM=`basename "$0"`
readonly VERSION=0.3
SEPARATE=0
VERBOSE=0
TARCMD=`command -v gtar || command -v gnutar || command -v tar`
FORMAT=tar
PREFIX=
TREEISH=HEAD
ARCHIVE_OPTS=
# RETURN VALUES/EXIT STATUS CODES
readonly E_BAD_OPTION=254
readonly E_UNKNOWN=255
# Process command-line arguments.
while test $# -gt 0; do
if [ x"$1" == x"--" ]; then
# detect argument termination
shift
break
fi
case $1 in
--format )
shift
FORMAT="$1"
shift
;;
--prefix )
shift
PREFIX="$1"
shift
;;
--worktree-attributes )
ARCHIVE_OPTS+=" $1"
shift
;;
--separate | -s )
shift
SEPARATE=1
;;
--tree-ish | -t )
shift
TREEISH="$1"
shift
;;
--version )
version
exit
;;
--verbose | -v )
shift
VERBOSE=1
;;
-? | --usage | --help )
usage
exit
;;
-* )
echo "Unrecognized option: $1" >&2
usage
exit $E_BAD_OPTION
;;
* )
break
;;
esac
done
OLD_PWD="`pwd`"
TMPDIR=${TMPDIR:-/tmp}
TMPFILE=`mktemp "$TMPDIR/$PROGRAM.XXXXXX"` # Create a place to store our work's progress
TMPLIST=`mktemp "$TMPDIR/$PROGRAM.submodules.XXXXXX"`
TOARCHIVE=`mktemp "$TMPDIR/$PROGRAM.toarchive.XXXXXX"`
OUT_FILE=$OLD_PWD # assume "this directory" without a name change by default
if [ ! -z "$1" ]; then
OUT_FILE="$1"
if [ "-" == "$OUT_FILE" ]; then
SEPARATE=0
fi
shift
fi
# Validate parameters; error early, error often.
if [ "-" == "$OUT_FILE" -o $SEPARATE -ne 1 ] && [ "$FORMAT" == "tar" -a `$TARCMD --help | grep -q -- "--concatenate"; echo $?` -ne 0 ]; then
echo "Your 'tar' does not support the '--concatenate' option, which we need"
echo "to produce a single tarfile. Either install a compatible tar (such as"
echo "gnutar), or invoke $PROGRAM with the '--separate' option."
exit
elif [ $SEPARATE -eq 1 -a ! -d "$OUT_FILE" ]; then
echo "When creating multiple archives, your destination must be a directory."
echo "If it's not, you risk being surprised when your files are overwritten."
exit
elif [ `git config -l | grep -q '^core\.bare=true'; echo $?` -eq 0 ]; then
echo "$PROGRAM must be run from a git working copy (i.e., not a bare repository)."
exit
fi
# Create the superproject's git-archive
if [ $VERBOSE -eq 1 ]; then
echo -n "creating superproject archive..."
fi
git archive --format=$FORMAT --prefix="$PREFIX" $ARCHIVE_OPTS $TREEISH > $TMPDIR/$(basename "$(pwd)").$FORMAT
if [ $VERBOSE -eq 1 ]; then
echo "done"
fi
echo $TMPDIR/$(basename "$(pwd)").$FORMAT >| $TMPFILE # clobber on purpose
superfile=`head -n 1 $TMPFILE`
if [ $VERBOSE -eq 1 ]; then
echo -n "looking for subprojects..."
fi
# find all '.git' dirs, these show us the remaining to-be-archived dirs
# we only want directories that are below the current directory
find . -mindepth 2 -name '.git' -type d -print | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
# as of version 1.7.8, git places the submodule .git directories under the superprojects .git dir
# the submodules get a .git file that points to their .git dir. we need to find all of these too
find . -mindepth 2 -name '.git' -type f -print | xargs grep -l "gitdir" | sed -e 's/^\.\///' -e 's/\.git$//' >> $TOARCHIVE
if [ $VERBOSE -eq 1 ]; then
echo "done"
echo " found:"
cat $TOARCHIVE | while read arch
do
echo " $arch"
done
fi
if [ $VERBOSE -eq 1 ]; then
echo -n "archiving submodules..."
fi
git submodule >>"$TMPLIST"
while read path; do
TREEISH=$(grep "^ .*${path%/} " "$TMPLIST" | cut -d ' ' -f 2) # git submodule does not list trailing slashes in $path
cd "$path"
git archive --format=$FORMAT --prefix="${PREFIX}$path" $ARCHIVE_OPTS ${TREEISH:-HEAD} > "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT
if [ $FORMAT == 'zip' ]; then
# delete the empty directory entry; zipped submodules won't unzip if we don't do this
zip -d "$(tail -n 1 $TMPFILE)" "${PREFIX}${path%/}" >/dev/null # remove trailing '/'
fi
echo "$TMPDIR"/"$(echo "$path" | sed -e 's/\//./g')"$FORMAT >> $TMPFILE
cd "$OLD_PWD"
done < $TOARCHIVE
if [ $VERBOSE -eq 1 ]; then
echo "done"
fi
if [ $VERBOSE -eq 1 ]; then
echo -n "concatenating archives into single archive..."
fi
# Concatenate archives into a super-archive.
if [ $SEPARATE -eq 0 -o "-" == "$OUT_FILE" ]; then
if [ $FORMAT == 'tar.gz' ]; then
gunzip $superfile
superfile=${superfile:0: -3} # Remove '.gz'
sed -e '1d' $TMPFILE | while read file; do
gunzip $file
file=${file:0: -3}
$TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
done
gzip $superfile
superfile=$superfile.gz
elif [ $FORMAT == 'tar' ]; then
sed -e '1d' $TMPFILE | while read file; do
$TARCMD --concatenate -f "$superfile" "$file" && rm -f "$file"
done
elif [ $FORMAT == 'zip' ]; then
sed -e '1d' $TMPFILE | while read file; do
# zip incorrectly stores the full path, so cd and then grow
cd `dirname "$file"`
zip -g "$superfile" `basename "$file"` && rm -f "$file"
done
cd "$OLD_PWD"
fi
echo "$superfile" >| $TMPFILE # clobber on purpose
fi
if [ $VERBOSE -eq 1 ]; then
echo "done"
fi
if [ $VERBOSE -eq 1 ]; then
echo -n "moving archive to $OUT_FILE..."
fi
while read file; do
if [ "-" == "$OUT_FILE" ]; then
cat "$file" && rm -f "$file"
else
mv "$file" "$OUT_FILE"
fi
done < $TMPFILE
if [ $VERBOSE -eq 1 ]; then
echo "done"
fi

View File

@ -2,21 +2,23 @@
set -e
# Simple script to construct a redistributable and complete tarball of the
# gamemode tree, including the git submodules, so that it can be trivially
# gamemode tree, including the subprojects, so that it can be trivially
# packaged by distributions banning networking during build.
#
# Modified from Ikey Doherty's release scripts for use within
# Feral Interactive's gamemode project.
git submodule init
git submodule update
# Bump in tandem with meson.build, run script once new tag is up.
VERSION="1.3"
NAME="gamemode"
./scripts/git-archive-all.sh --format tar --prefix ${NAME}-${VERSION}/ --verbose -t HEAD ${NAME}-${VERSION}.tar
VERSION=$(git describe --tags --dirty)
# get code in this repo
git archive HEAD --format=tar --prefix=${NAME}-${VERSION}/ --output=${NAME}-${VERSION}.tar
# get code from subprojects
meson subprojects download
meson subprojects update --reset
tar -rf ${NAME}-${VERSION}.tar --exclude-vcs --transform="s,^subprojects,${NAME}-$VERSION/subprojects," subprojects/inih-r54/
# compress archive
xz -9 "${NAME}-${VERSION}.tar"
# Automatically sign the tarball with GPG key of user running this script
gpg --armor --detach-sign "${NAME}-${VERSION}.tar.xz"
gpg --verify "${NAME}-${VERSION}.tar.xz.asc"
sha256sum "${NAME}-${VERSION}.tar.xz" "${NAME}-${VERSION}.tar.xz.asc" > sha256sums.txt

View File

@ -0,0 +1,12 @@
#!/bin/bash
set -exo pipefail
# Ensure we are at the project root
cd "$(dirname $0)"/..
# Collect scan-build output
ninja scan-build -C builddir | tee builddir/meson-logs/scan-build.txt
# Invert the output - if this string exists it's a fail
! grep -E '[0-9]+ bugs? found.' builddir/meson-logs/scan-build.txt

@ -1 +0,0 @@
Subproject commit 745ada6724038cde32ff6390b32426cbdd5e532b

9
subprojects/inih.wrap Normal file
View File

@ -0,0 +1,9 @@
[wrap-file]
directory = inih-r60
source_url = https://github.com/benhoyt/inih/archive/r60.tar.gz
source_filename = inih-r60.tar.gz
source_hash = 706aa05c888b53bd170e5d8aa8f8a9d9ccf5449dfed262d5103d1f292af26774
[provide]
inih = inih_dep
inireader = INIReader_dep

141
util/cpucorectl.c Normal file
View File

@ -0,0 +1,141 @@
/*
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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.
*/
#define _GNU_SOURCE
#include <linux/limits.h>
#include <sched.h>
#include <unistd.h>
#include "common-cpu.h"
#include "common-logging.h"
static int write_state(char *path, int state)
{
FILE *f = fopen(path, "w");
if (!f) {
LOG_ERROR("Couldn't open file at %s (%s)\n", path, strerror(errno));
return 0;
}
if (putc(state, f) == EOF) {
LOG_ERROR("Couldn't write to file at %s (%s)\n", path, strerror(errno));
fclose(f);
return 0;
}
fclose(f);
return 1;
}
static void log_state(const int state, const long first, const long last)
{
if (state == '0') {
if (first == last)
LOG_MSG("parked core %ld\n", first);
else
LOG_MSG("parked cores %ld - %ld\n", first, last);
} else {
if (first == last)
LOG_MSG("unparked core %ld\n", first);
else
LOG_MSG("unparked cores %ld - %ld\n", first, last);
}
}
static int set_state(char *cpulist, int state)
{
char path[PATH_MAX];
long from, to;
char *list = cpulist;
long first = -1, last = -1;
while ((list = parse_cpulist(list, &from, &to))) {
for (long cpu = from; cpu < to + 1; cpu++) {
if (snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/online", cpu) < 0) {
LOG_ERROR("snprintf failed, will not apply cpu core parking!\n");
return 0;
}
if (!write_state(path, state)) {
/* on some systems one cannot park core #0 */
if (cpu != 0) {
if (state == '0') {
LOG_ERROR("unable to park core #%ld, will not apply cpu core parking!\n",
cpu);
return -1;
}
LOG_ERROR("unable to unpark core #%ld\n", cpu);
}
} else {
if (first == -1) {
first = cpu;
last = cpu;
} else if (last + 1 == cpu) {
last = cpu;
} else {
log_state(state, first, last);
first = cpu;
last = cpu;
}
}
}
}
if (first != -1)
log_state(state, first, last);
return 1;
}
int main(int argc, char *argv[])
{
if (geteuid() != 0) {
LOG_ERROR("This program must be run as root\n");
return EXIT_FAILURE;
}
if (argc == 3 && strncmp(argv[1], "online", 6) == 0) {
if (!set_state(argv[2], '1'))
return EXIT_FAILURE;
} else if (argc == 3 && strncmp(argv[1], "offline", 7) == 0) {
if (!set_state(argv[2], '0'))
return EXIT_FAILURE;
} else {
fprintf(stderr, "usage: cpucorectl [online]|[offline] VALUE]\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,12 +31,10 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "governors-query.h"
#include "logging.h"
#include "common-governors.h"
#include "common-logging.h"
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
/**
* Sets all governors to a value

View File

@ -1,6 +1,6 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -31,10 +31,12 @@ POSSIBILITY OF SUCH DAMAGE.
#define _GNU_SOURCE
#include "logging.h"
#include "common-external.h"
#include "common-gpu.h"
#include "common-logging.h"
#include "external-helper.h"
#include "gpu-control.h"
#include <limits.h>
#include <unistd.h>
/* NV constants */
#define NV_CORE_OFFSET_ATTRIBUTE "GPUGraphicsClockOffset"
@ -44,6 +46,7 @@ POSSIBILITY OF SUCH DAMAGE.
#define NV_PCIDEVICE_ATTRIBUTE "PCIDevice"
#define NV_ATTRIBUTE_FORMAT "[gpu:%ld]/%s"
#define NV_PERF_LEVEL_FORMAT "[%ld]"
#define NV_ARG_MAX 128
/* AMD constants */
#define AMD_DRM_PATH "/sys/class/drm/card%ld/device/%s"
@ -64,6 +67,30 @@ static void print_usage_and_exit(void)
exit(EXIT_FAILURE);
}
static const char *get_nv_attr(const char *attr)
{
static char out[EXTERNAL_BUFFER_MAX];
const char *exec_args[] = { "nvidia-settings", "-q", attr, "-t", NULL };
if (run_external_process(exec_args, out, -1) != 0) {
LOG_ERROR("Failed to get %s!\n", attr);
out[0] = 0;
return NULL;
}
return &out[0];
}
static int set_nv_attr(const char *attr)
{
const char *exec_args_core[] = { "nvidia-settings", "-a", attr, NULL };
if (run_external_process(exec_args_core, NULL, -1) != 0) {
LOG_ERROR("Failed to set %s!\n", attr);
return -1;
}
return 0;
}
/* Get the nvidia driver index for the current GPU */
static long get_gpu_index_id_nv(struct GameModeGPUInfo *info)
{
@ -108,23 +135,21 @@ static long get_max_perf_level_nv(struct GameModeGPUInfo *info)
if (!getenv("DISPLAY"))
LOG_ERROR("Getting Nvidia parameters requires DISPLAY to be set - will likely fail!\n");
char arg[128] = { 0 };
char buf[EXTERNAL_BUFFER_MAX] = { 0 };
char arg[NV_ARG_MAX] = { 0 };
const char *attr;
snprintf(arg, 128, NV_ATTRIBUTE_FORMAT, info->device, NV_PERFMODES_ATTRIBUTE);
const char *exec_args[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
if (run_external_process(exec_args, buf, -1) != 0) {
LOG_ERROR("Failed to get %s!\n", arg);
snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT, info->device, NV_PERFMODES_ATTRIBUTE);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
char *ptr = strrchr(buf, ';');
char *ptr = strrchr(attr, ';');
long level = -1;
if (!ptr || sscanf(ptr, "; perf=%ld", &level) != 1) {
LOG_ERROR(
"Output didn't match expected format, couldn't discern highest perf level from "
"nvidia-settings!\n");
LOG_ERROR("Output:%s\n", buf);
LOG_ERROR("Output:%s\n", attr);
return -1;
}
@ -142,59 +167,53 @@ static int get_gpu_state_nv(struct GameModeGPUInfo *info)
long perf_level = get_max_perf_level_nv(info);
char arg[128] = { 0 };
char buf[EXTERNAL_BUFFER_MAX] = { 0 };
char arg[NV_ARG_MAX] = { 0 };
const char *attr;
char *end;
/* Get the GPUGraphicsClockOffset parameter */
snprintf(arg,
128,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
info->device,
NV_CORE_OFFSET_ATTRIBUTE,
perf_level);
const char *exec_args_core[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
if (run_external_process(exec_args_core, buf, -1) != 0) {
LOG_ERROR("Failed to get %s!\n", arg);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
info->nv_core = strtol(buf, &end, 10);
if (end == buf) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, buf);
info->nv_core = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
/* Get the GPUMemoryTransferRateOffset parameter */
snprintf(arg,
128,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT,
info->device,
NV_MEM_OFFSET_ATTRIBUTE,
perf_level);
const char *exec_args_mem[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
if (run_external_process(exec_args_mem, buf, -1) != 0) {
LOG_ERROR("Failed to get %s!\n", arg);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
info->nv_mem = strtol(buf, &end, 10);
if (end == buf) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, buf);
info->nv_mem = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
/* Get the GPUPowerMizerMode parameter */
snprintf(arg, 128, NV_ATTRIBUTE_FORMAT, info->device, NV_POWERMIZER_MODE_ATTRIBUTE);
const char *exec_args_pm[] = { "/usr/bin/nvidia-settings", "-q", arg, "-t", NULL };
if (run_external_process(exec_args_pm, buf, -1) != 0) {
LOG_ERROR("Failed to get %s!\n", arg);
snprintf(arg, NV_ARG_MAX, NV_ATTRIBUTE_FORMAT, info->device, NV_POWERMIZER_MODE_ATTRIBUTE);
if ((attr = get_nv_attr(arg)) == NULL) {
return -1;
}
info->nv_powermizer_mode = strtol(buf, &end, 10);
if (end == buf) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, buf);
info->nv_powermizer_mode = strtol(attr, &end, 10);
if (end == attr) {
LOG_ERROR("Failed to parse output for \"%s\" output was \"%s\"!\n", arg, attr);
return -1;
}
@ -218,20 +237,18 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
long perf_level = get_max_perf_level_nv(info);
char arg[128] = { 0 };
char arg[NV_ARG_MAX] = { 0 };
/* Set the GPUGraphicsClockOffset parameter */
if (info->nv_core != -1) {
snprintf(arg,
128,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld",
info->device,
NV_CORE_OFFSET_ATTRIBUTE,
perf_level,
info->nv_core);
const char *exec_args_core[] = { "/usr/bin/nvidia-settings", "-a", arg, NULL };
if (run_external_process(exec_args_core, NULL, -1) != 0) {
LOG_ERROR("Failed to set %s!\n", arg);
if (set_nv_attr(arg) != 0) {
status = -1;
}
}
@ -239,15 +256,13 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
/* Set the GPUMemoryTransferRateOffset parameter */
if (info->nv_mem != -1) {
snprintf(arg,
128,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT NV_PERF_LEVEL_FORMAT "=%ld",
info->device,
NV_MEM_OFFSET_ATTRIBUTE,
perf_level,
info->nv_mem);
const char *exec_args_mem[] = { "/usr/bin/nvidia-settings", "-a", arg, NULL };
if (run_external_process(exec_args_mem, NULL, -1) != 0) {
LOG_ERROR("Failed to set %s!\n", arg);
if (set_nv_attr(arg) != 0) {
status = -1;
}
}
@ -255,14 +270,12 @@ static int set_gpu_state_nv(struct GameModeGPUInfo *info)
/* Set the GPUPowerMizerMode parameter if requested */
if (info->nv_powermizer_mode != -1) {
snprintf(arg,
128,
NV_ARG_MAX,
NV_ATTRIBUTE_FORMAT "=%ld",
info->device,
NV_POWERMIZER_MODE_ATTRIBUTE,
info->nv_powermizer_mode);
const char *exec_args_pm[] = { "/usr/bin/nvidia-settings", "-a", arg, NULL };
if (run_external_process(exec_args_pm, NULL, -1) != 0) {
LOG_ERROR("Failed to set %s!\n", arg);
if (set_nv_attr(arg) != 0) {
status = -1;
}
}
@ -280,8 +293,8 @@ static int get_gpu_state_amd(struct GameModeGPUInfo *info)
return -1;
/* Get the contents of power_dpm_force_performance_level */
char path[64];
snprintf(path, 64, AMD_DRM_PATH, info->device, "power_dpm_force_performance_level");
char path[PATH_MAX];
snprintf(path, PATH_MAX, AMD_DRM_PATH, info->device, "power_dpm_force_performance_level");
FILE *file = fopen(path, "r");
if (!file) {
@ -289,21 +302,26 @@ static int get_gpu_state_amd(struct GameModeGPUInfo *info)
return -1;
}
char buff[CONFIG_VALUE_MAX];
if (!fgets(buff, CONFIG_VALUE_MAX, file)) {
int ret = 0;
char buff[GPU_VALUE_MAX] = { 0 };
if (!fgets(buff, GPU_VALUE_MAX, file)) {
LOG_ERROR("Could not read file %s (%s)!\n", path, strerror(errno));
return -1;
ret = -1;
}
if (fclose(file) != 0) {
LOG_ERROR("Could not close %s after reading (%s)!\n", path, strerror(errno));
return -1;
ret = -1;
}
/* Copy in the value from the file */
strncpy(info->amd_performance_level, buff, CONFIG_VALUE_MAX);
if (ret == 0) {
/* Copy in the value from the file */
strncpy(info->amd_performance_level, buff, GPU_VALUE_MAX - 1);
info->amd_performance_level[GPU_VALUE_MAX - 1] = '\0';
}
return info == NULL;
return ret;
}
/*
@ -311,8 +329,8 @@ static int get_gpu_state_amd(struct GameModeGPUInfo *info)
*/
static int set_gpu_state_amd_file(const char *filename, long device, const char *value)
{
char path[64];
snprintf(path, 64, AMD_DRM_PATH, device, filename);
char path[PATH_MAX];
snprintf(path, PATH_MAX, AMD_DRM_PATH, device, filename);
FILE *file = fopen(path, "w");
if (!file) {
@ -320,17 +338,19 @@ static int set_gpu_state_amd_file(const char *filename, long device, const char
return -1;
}
int ret = 0;
if (fprintf(file, "%s", value) < 0) {
LOG_ERROR("Could not write to %s (%s)!\n", path, strerror(errno));
return -1;
ret = -1;
}
if (fclose(file) != 0) {
LOG_ERROR("Could not close %s after writing (%s)!\n", path, strerror(errno));
return -1;
ret = -1;
}
return 0;
return ret;
}
/**
@ -390,10 +410,11 @@ static long get_generic_value(const char *val)
*/
int main(int argc, char *argv[])
{
struct GameModeGPUInfo info;
memset(&info, 0, sizeof(info));
if (argc == 3 && strncmp(argv[2], "get", 3) == 0) {
/* Get and verify the vendor and device */
struct GameModeGPUInfo info;
memset(&info, 0, sizeof(info));
info.device = get_device(argv[1]);
info.vendor = gamemode_get_gpu_vendor(info.device);
@ -414,14 +435,12 @@ int main(int argc, char *argv[])
break;
default:
LOG_ERROR("Currently unsupported GPU vendor 0x%04x, doing nothing!\n",
(short)info.vendor);
(unsigned short)info.vendor);
break;
}
} else if (argc >= 4 && argc <= 7 && strncmp(argv[2], "set", 3) == 0) {
/* Get and verify the vendor and device */
struct GameModeGPUInfo info;
memset(&info, 0, sizeof(info));
info.device = get_device(argv[1]);
info.vendor = gamemode_get_gpu_vendor(info.device);
@ -449,12 +468,12 @@ int main(int argc, char *argv[])
LOG_ERROR("Must pass performance level for AMD gpu!\n");
print_usage_and_exit();
}
strncpy(info.amd_performance_level, argv[3], CONFIG_VALUE_MAX);
strncpy(info.amd_performance_level, argv[3], GPU_VALUE_MAX - 1);
return set_gpu_state_amd(&info);
break;
default:
LOG_ERROR("Currently unsupported GPU vendor 0x%04x, doing nothing!\n",
(short)info.vendor);
(unsigned short)info.vendor);
print_usage_and_exit();
break;
}

75
util/meson.build Normal file
View File

@ -0,0 +1,75 @@
# Small target util to get and set cpu governors
cpugovctl_sources = [
'cpugovctl.c',
]
cpugovctl = executable(
'cpugovctl',
sources: cpugovctl_sources,
dependencies: [
link_daemon_common,
],
install: true,
install_dir: path_libexecdir,
)
# Small target util to get and set gpu clocks values
gpuclockctl_sources = [
'gpuclockctl.c',
]
gpuclockctl = executable(
'gpuclockctl',
sources: gpuclockctl_sources,
dependencies: [
link_daemon_common,
],
install: true,
install_dir: path_libexecdir,
)
# Small target util to park and unpark cores
cpucorectl_sources = [
'cpucorectl.c',
]
cpucorectl = executable(
'cpucorectl',
sources: cpucorectl_sources,
dependencies: [
link_daemon_common,
],
install: true,
install_dir: path_libexecdir,
)
# Small target util to set values in /proc/sys/
procsysctl_sources = [
'procsysctl.c',
]
procsysctl = executable(
'procsysctl',
sources: procsysctl_sources,
dependencies: [
link_daemon_common,
],
install: true,
install_dir: path_libexecdir,
)
# Small target util to get and set platform profile
platprofctl_sources = [
'platprofctl.c',
]
platprofctl = executable(
'platprofctl',
sources: platprofctl_sources,
dependencies: [
link_daemon_common,
],
install: true,
install_dir: path_libexecdir,
)

84
util/platprofctl.c Normal file
View File

@ -0,0 +1,84 @@
/*
Copyright (c) 2025, the GameMode contributors
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.
*/
#define _GNU_SOURCE
#include "common-logging.h"
#include "common-profile.h"
#include <unistd.h>
/**
* Sets platform profile to a value
*/
static int set_profile_state(const char *value)
{
int retval = EXIT_SUCCESS;
FILE *f = fopen(profile_path, "w");
if (!f) {
LOG_ERROR("Failed to open file for write %s\n", profile_path);
return EXIT_FAILURE;
}
if (fprintf(f, "%s\n", value) < 0) {
LOG_ERROR("Failed to set platform profile to %s: %s", value, strerror(errno));
retval = EXIT_FAILURE;
}
fclose(f);
return retval;
}
/**
* Main entry point, dispatch to the appropriate helper
*/
int main(int argc, char *argv[])
{
if (argc == 2 && strncmp(argv[1], "get", 3) == 0) {
printf("%s", get_profile_state());
} else if (argc == 3 && strncmp(argv[1], "set", 3) == 0) {
const char *value = argv[2];
/* Must be root to set the state */
if (geteuid() != 0) {
LOG_ERROR("This program must be run as root\n");
return EXIT_FAILURE;
}
return set_profile_state(value);
} else {
fprintf(stderr, "usage: platprofctl [get] [set VALUE]\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

View File

@ -1,11 +1,8 @@
/*
Copyright (c) 2017-2019, Feral Interactive
Copyright (c) 2017-2025, Feral Interactive and the GameMode contributors
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
@ -14,7 +11,6 @@ modification, are permitted provided that the following conditions are met:
* 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
@ -26,36 +22,54 @@ 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.
*/
#define _GNU_SOURCE
#include "gamemode.h"
#include "helpers.h"
#include <fcntl.h>
#include <linux/limits.h>
#include <unistd.h>
#include "common-logging.h"
#include "common-splitlock.h"
/**
* Opens the process environment for a specific PID and returns
* a file descriptor to the directory /proc/PID. Doing it that way prevents
* the directory going MIA when a process exits while we are looking at it
* and allows us to handle fewer error cases.
*/
procfd_t game_mode_open_proc(const pid_t pid)
static bool write_value(const char *key, const char *value)
{
char buffer[PATH_MAX];
const char *proc_path = buffered_snprintf(buffer, "/proc/%d", pid);
FILE *f = fopen(key, "w");
return proc_path ? open(proc_path, O_RDONLY | O_CLOEXEC) : INVALID_PROCFD;
if (!f) {
if (errno != ENOENT)
LOG_ERROR("Couldn't open file at %s (%s)\n", key, strerror(errno));
return false;
}
if (fputs(value, f) == EOF) {
LOG_ERROR("Couldn't write to file at %s (%s)\n", key, strerror(errno));
fclose(f);
return false;
}
fclose(f);
return true;
}
/**
* Closes the process environment.
*/
int game_mode_close_proc(const procfd_t procfd)
int main(int argc, char *argv[])
{
return close(procfd);
if (geteuid() != 0) {
LOG_ERROR("This program must be run as root\n");
return EXIT_FAILURE;
}
if (argc == 3) {
if (strcmp(argv[1], "split_lock_mitigate") == 0) {
if (!write_value(splitlock_path, argv[2]))
return EXIT_FAILURE;
return EXIT_SUCCESS;
} else {
fprintf(stderr, "unsupported key: '%s'\n", argv[1]);
return EXIT_FAILURE;
}
}
fprintf(stderr, "usage: procsysctl KEY VALUE\n");
fprintf(stderr, "where KEY can by any of 'split_lock_mitigate'\n");
return EXIT_FAILURE;
}