blob: f1b862f6278e70dc3bfef4dba575f0e6bbaf4ee1 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
|
{
config,
lib,
pkgs,
...
}:
let
cfg = config.programs.opengamepadui;
gamescopeCfg = config.programs.gamescope;
opengamepadui-gamescope =
let
exports = lib.mapAttrsToList (n: v: "export ${n}=${v}") cfg.gamescopeSession.env;
in
# Based on gamescope-session-plus from ChimeraOS
pkgs.writeShellScriptBin "opengamepadui-gamescope" ''
${builtins.concatStringsSep "\n" exports}
# Enable Mangoapp
export MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX)
export RADV_FORCE_VRS_CONFIG_FILE=$(mktemp /tmp/radv_vrs.XXXXXXXX)
# Plop GAMESCOPE_MODE_SAVE_FILE into $XDG_CONFIG_HOME (defaults to ~/.config).
export GAMESCOPE_MODE_SAVE_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/modes.cfg"
export GAMESCOPE_PATCHED_EDID_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/edid.bin"
# Make path to gamescope mode save file.
mkdir -p "$(dirname "$GAMESCOPE_MODE_SAVE_FILE")"
touch "$GAMESCOPE_MODE_SAVE_FILE"
# Make path to Gamescope edid patched file.
mkdir -p "$(dirname "$GAMESCOPE_PATCHED_EDID_FILE")"
touch "$GAMESCOPE_PATCHED_EDID_FILE"
# Initially write no_display to our config file
# so we don't get mangoapp showing up before OpenGamepadUI initializes
# on OOBE and stuff.
mkdir -p "$(dirname "$MANGOHUD_CONFIGFILE")"
echo "no_display" >"$MANGOHUD_CONFIGFILE"
# Prepare our initial VRS config file
# for dynamic VRS in Mesa.
mkdir -p "$(dirname "$RADV_FORCE_VRS_CONFIG_FILE")"
echo "1x1" >"$RADV_FORCE_VRS_CONFIG_FILE"
# To play nice with the short term callback-based limiter for now
export GAMESCOPE_LIMITER_FILE=$(mktemp /tmp/gamescope-limiter.XXXXXXXX)
ulimit -n 524288
# Setup socket for gamescope
# Create run directory file for startup and stats sockets
tmpdir="$([[ -n ''${XDG_RUNTIME_DIR+x} ]] && mktemp -p "$XDG_RUNTIME_DIR" -d -t gamescope.XXXXXXX)"
socket="''${tmpdir:+$tmpdir/startup.socket}"
stats="''${tmpdir:+$tmpdir/stats.pipe}"
# Fail early if we don't have a proper runtime directory setup
if [[ -z $tmpdir || -z ''${XDG_RUNTIME_DIR+x} ]]; then
echo >&2 "!! Failed to find run directory in which to create stats session sockets (is \$XDG_RUNTIME_DIR set?)"
exit 0
fi
export GAMESCOPE_STATS="$stats"
mkfifo -- "$stats"
mkfifo -- "$socket"
# Start gamescope compositor, log it's output and background it
echo gamescope ${lib.escapeShellArgs cfg.gamescopeSession.args} -R $socket -T $stats >"$HOME"/.gamescope-cmd.log
gamescope ${lib.escapeShellArgs cfg.gamescopeSession.args} -R $socket -T $stats >"$HOME"/.gamescope-stdout.log 2>&1 &
gamescope_pid="$!"
if read -r -t 3 response_x_display response_wl_display <>"$socket"; then
export DISPLAY="$response_x_display"
export GAMESCOPE_WAYLAND_DISPLAY="$response_wl_display"
# We're done!
else
echo "gamescope failed"
kill -9 "$gamescope_pid"
wait -n "$gamescope_pid"
exit 1
# Systemd or Session manager will have to restart session
fi
# If we have mangoapp binary start it
if command -v mangoapp >/dev/null; then
(while true; do
sleep 1
mangoapp >"$HOME"/.mangoapp-stdout.log 2>&1
done) &
fi
# Start OpenGamepadUI
opengamepadui ${lib.escapeShellArgs cfg.args}
# When the client exits, kill gamescope nicely
kill $gamescope_pid
'';
gamescopeSessionFile =
(pkgs.writeTextDir "share/wayland-sessions/opengamepadui.desktop" ''
[Desktop Entry]
Name=opengamepadui
Comment=OpenGamepadUI Session
Exec=${opengamepadui-gamescope}/bin/opengamepadui-gamescope
Type=Application
'').overrideAttrs
(_: {
passthru.providedSessions = [ "opengamepadui" ];
});
in
{
options.programs.opengamepadui = {
enable = lib.mkEnableOption "opengamepadui";
args = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Arguments to be passed to OpenGamepadUI
'';
};
package = lib.mkPackageOption pkgs "OpenGamepadUI" {
default = [ "opengamepadui" ];
};
extraPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = [ ];
example = lib.literalExpression ''
with pkgs; [
gamescope
]
'';
description = ''
Additional packages to add to the OpenGamepadUI environment.
'';
};
fontPackages = lib.mkOption {
type = lib.types.listOf lib.types.package;
default = config.fonts.packages;
defaultText = lib.literalExpression "builtins.filter lib.types.package.check config.fonts.packages";
example = lib.literalExpression "with pkgs; [ source-han-sans ]";
description = ''
Font packages to use in OpenGamepadUI.
Defaults to system fonts, but could be overridden to use other fonts — useful for users who would like to customize CJK fonts used in opengamepadui. According to the [upstream issue](https://github.com/ValveSoftware/opengamepadui-for-linux/issues/10422#issuecomment-1944396010), opengamepadui only follows the per-user fontconfig configuration.
'';
};
gamescopeSession = lib.mkOption {
description = "Run a GameScope driven OpenGamepadUI session from your display-manager";
default = { };
type = lib.types.submodule {
options = {
enable = lib.mkEnableOption "GameScope Session";
args = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [
"--prefer-output"
"*,eDP-1"
"--xwayland-count"
"2"
"--default-touch-mode"
"4"
"--hide-cursor-delay"
"3000"
"--fade-out-duration"
"200"
"--steam"
];
description = ''
Arguments to be passed to GameScope for the session.
'';
};
env = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
description = ''
Environmental variables to be passed to GameScope for the session.
'';
};
};
};
};
inputplumber.enable = lib.mkEnableOption ''
Run InputPlumber service for input management and gamepad configuration.
'';
powerstation.enable = lib.mkEnableOption ''
Run PowerStation service for TDP control and performance settings.
'';
};
config = lib.mkIf cfg.enable {
hardware.graphics = {
# this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
enable = true;
enable32Bit = pkgs.stdenv.hostPlatform.isx86_64;
};
security.wrappers = lib.mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
# needed or steam plugin fails
bwrap = {
owner = "root";
group = "root";
source = lib.getExe pkgs.bubblewrap;
setuid = true;
};
};
programs.opengamepadui.extraPackages = cfg.fontPackages;
programs.gamescope.enable = true;
services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [
gamescopeSessionFile
];
programs.opengamepadui.gamescopeSession.env = {
# Fix intel color corruption
# might come with some performance degradation but is better than a corrupted
# color image
INTEL_DEBUG = "norbc";
mesa_glthread = "true";
# This should be used by default by gamescope. Cannot hurt to force it anyway.
# Reported better framelimiting with this enabled
ENABLE_GAMESCOPE_WSI = "1";
# Force Qt applications to run under xwayland
QT_QPA_PLATFORM = "xcb";
# Some environment variables by default (taken from Deck session)
SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS = "0";
# There is no way to set a color space for an NV12
# buffer in Wayland. And the color management protocol that is
# meant to let this happen is missing the color range...
# So just workaround this with an ENV var that Remote Play Together
# and Gamescope will use for now.
GAMESCOPE_NV12_COLORSPACE = "k_EStreamColorspace_BT601";
# Workaround older versions of vkd3d-proton setting this
# too low (desc.BufferCount), resulting in symptoms that are potentially like
# swapchain starvation.
VKD3D_SWAPCHAIN_LATENCY_FRAMES = "3";
# To expose vram info from radv
WINEDLLOVERRIDES = "dxgi=n";
# Don't wait for buffers to idle on the client side before sending them to gamescope
vk_xwayland_wait_ready = "false";
# Temporary crutch until dummy plane interactions / etc are figured out
GAMESCOPE_DISABLE_ASYNC_FLIPS = "1";
};
# optionally enable 32bit pulseaudio support if pulseaudio is enabled
services.pulseaudio.support32Bit = config.services.pulseaudio.enable;
services.pipewire.alsa.support32Bit = config.services.pipewire.alsa.enable;
hardware.steam-hardware.enable = true;
services.inputplumber.enable = lib.mkDefault cfg.inputplumber.enable;
services.powerstation.enable = lib.mkDefault cfg.powerstation.enable;
environment.pathsToLink = [ "/share" ];
environment.systemPackages = [
cfg.package
]
++ lib.optional cfg.gamescopeSession.enable opengamepadui-gamescope;
};
meta.maintainers = with lib.maintainers; [ shadowapex ];
}
|