···
9
+
cfg = config.programs.opengamepadui;
10
+
gamescopeCfg = config.programs.gamescope;
12
+
opengamepadui-gamescope =
14
+
exports = lib.mapAttrsToList (n: v: "export ${n}=${v}") cfg.gamescopeSession.env;
16
+
# Based on gamescope-session-plus from ChimeraOS
17
+
pkgs.writeShellScriptBin "opengamepadui-gamescope" ''
18
+
${builtins.concatStringsSep "\n" exports}
21
+
export MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX)
22
+
export RADV_FORCE_VRS_CONFIG_FILE=$(mktemp /tmp/radv_vrs.XXXXXXXX)
24
+
# Plop GAMESCOPE_MODE_SAVE_FILE into $XDG_CONFIG_HOME (defaults to ~/.config).
25
+
export GAMESCOPE_MODE_SAVE_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/modes.cfg"
26
+
export GAMESCOPE_PATCHED_EDID_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/gamescope/edid.bin"
28
+
# Make path to gamescope mode save file.
29
+
mkdir -p "$(dirname "$GAMESCOPE_MODE_SAVE_FILE")"
30
+
touch "$GAMESCOPE_MODE_SAVE_FILE"
32
+
# Make path to Gamescope edid patched file.
33
+
mkdir -p "$(dirname "$GAMESCOPE_PATCHED_EDID_FILE")"
34
+
touch "$GAMESCOPE_PATCHED_EDID_FILE"
36
+
# Initially write no_display to our config file
37
+
# so we don't get mangoapp showing up before OpenGamepadUI initializes
38
+
# on OOBE and stuff.
39
+
mkdir -p "$(dirname "$MANGOHUD_CONFIGFILE")"
40
+
echo "no_display" >"$MANGOHUD_CONFIGFILE"
42
+
# Prepare our initial VRS config file
43
+
# for dynamic VRS in Mesa.
44
+
mkdir -p "$(dirname "$RADV_FORCE_VRS_CONFIG_FILE")"
45
+
echo "1x1" >"$RADV_FORCE_VRS_CONFIG_FILE"
47
+
# To play nice with the short term callback-based limiter for now
48
+
export GAMESCOPE_LIMITER_FILE=$(mktemp /tmp/gamescope-limiter.XXXXXXXX)
52
+
# Setup socket for gamescope
53
+
# Create run directory file for startup and stats sockets
54
+
tmpdir="$([[ -n ''${XDG_RUNTIME_DIR+x} ]] && mktemp -p "$XDG_RUNTIME_DIR" -d -t gamescope.XXXXXXX)"
55
+
socket="''${tmpdir:+$tmpdir/startup.socket}"
56
+
stats="''${tmpdir:+$tmpdir/stats.pipe}"
58
+
# Fail early if we don't have a proper runtime directory setup
59
+
if [[ -z $tmpdir || -z ''${XDG_RUNTIME_DIR+x} ]]; then
60
+
echo >&2 "!! Failed to find run directory in which to create stats session sockets (is \$XDG_RUNTIME_DIR set?)"
64
+
export GAMESCOPE_STATS="$stats"
68
+
# Start gamescope compositor, log it's output and background it
69
+
echo gamescope ${lib.escapeShellArgs cfg.gamescopeSession.args} -R $socket -T $stats >"$HOME"/.gamescope-cmd.log
70
+
gamescope ${lib.escapeShellArgs cfg.gamescopeSession.args} -R $socket -T $stats >"$HOME"/.gamescope-stdout.log 2>&1 &
73
+
if read -r -t 3 response_x_display response_wl_display <>"$socket"; then
74
+
export DISPLAY="$response_x_display"
75
+
export GAMESCOPE_WAYLAND_DISPLAY="$response_wl_display"
78
+
echo "gamescope failed"
79
+
kill -9 "$gamescope_pid"
80
+
wait -n "$gamescope_pid"
82
+
# Systemd or Session manager will have to restart session
85
+
# If we have mangoapp binary start it
86
+
if command -v mangoapp >/dev/null; then
89
+
mangoapp >"$HOME"/.mangoapp-stdout.log 2>&1
93
+
# Start OpenGamepadUI
94
+
opengamepadui ${lib.escapeShellArgs cfg.args}
96
+
# When the client exits, kill gamescope nicely
100
+
gamescopeSessionFile =
101
+
(pkgs.writeTextDir "share/wayland-sessions/opengamepadui.desktop" ''
104
+
Comment=OpenGamepadUI Session
105
+
Exec=${opengamepadui-gamescope}/bin/opengamepadui-gamescope
109
+
passthru.providedSessions = [ "opengamepadui" ];
113
+
options.programs.opengamepadui = {
114
+
enable = lib.mkEnableOption "opengamepadui";
116
+
args = lib.mkOption {
117
+
type = lib.types.listOf lib.types.str;
120
+
Arguments to be passed to OpenGamepadUI
124
+
package = lib.mkPackageOption pkgs "OpenGamepadUI" {
125
+
default = [ "opengamepadui" ];
128
+
extraPackages = lib.mkOption {
129
+
type = lib.types.listOf lib.types.package;
131
+
example = lib.literalExpression ''
137
+
Additional packages to add to the OpenGamepadUI environment.
141
+
fontPackages = lib.mkOption {
142
+
type = lib.types.listOf lib.types.package;
143
+
default = config.fonts.packages;
144
+
defaultText = lib.literalExpression "builtins.filter lib.types.package.check config.fonts.packages";
145
+
example = lib.literalExpression "with pkgs; [ source-han-sans ]";
147
+
Font packages to use in OpenGamepadUI.
149
+
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.
153
+
gamescopeSession = lib.mkOption {
154
+
description = "Run a GameScope driven OpenGamepadUI session from your display-manager";
156
+
type = lib.types.submodule {
158
+
enable = lib.mkEnableOption "GameScope Session";
159
+
args = lib.mkOption {
160
+
type = lib.types.listOf lib.types.str;
166
+
"--default-touch-mode"
168
+
"--hide-cursor-delay"
170
+
"--fade-out-duration"
175
+
Arguments to be passed to GameScope for the session.
179
+
env = lib.mkOption {
180
+
type = lib.types.attrsOf lib.types.str;
183
+
Environmental variables to be passed to GameScope for the session.
190
+
inputplumber.enable = lib.mkEnableOption ''
191
+
Run InputPlumber service for input management and gamepad configuration.
194
+
powerstation.enable = lib.mkEnableOption ''
195
+
Run PowerStation service for TDP control and performance settings.
199
+
config = lib.mkIf cfg.enable {
200
+
hardware.graphics = {
201
+
# this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
203
+
enable32Bit = pkgs.stdenv.hostPlatform.isx86_64;
206
+
security.wrappers = lib.mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
207
+
# needed or steam plugin fails
211
+
source = lib.getExe pkgs.bubblewrap;
216
+
programs.opengamepadui.extraPackages = cfg.fontPackages;
218
+
programs.gamescope.enable = true;
219
+
services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [
220
+
gamescopeSessionFile
223
+
programs.opengamepadui.gamescopeSession.env = {
224
+
# Fix intel color corruption
225
+
# might come with some performance degradation but is better than a corrupted
227
+
INTEL_DEBUG = "norbc";
228
+
mesa_glthread = "true";
229
+
# This should be used by default by gamescope. Cannot hurt to force it anyway.
230
+
# Reported better framelimiting with this enabled
231
+
ENABLE_GAMESCOPE_WSI = "1";
232
+
# Force Qt applications to run under xwayland
233
+
QT_QPA_PLATFORM = "xcb";
234
+
# Some environment variables by default (taken from Deck session)
235
+
SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS = "0";
236
+
# There is no way to set a color space for an NV12
237
+
# buffer in Wayland. And the color management protocol that is
238
+
# meant to let this happen is missing the color range...
239
+
# So just workaround this with an ENV var that Remote Play Together
240
+
# and Gamescope will use for now.
241
+
GAMESCOPE_NV12_COLORSPACE = "k_EStreamColorspace_BT601";
242
+
# Workaround older versions of vkd3d-proton setting this
243
+
# too low (desc.BufferCount), resulting in symptoms that are potentially like
244
+
# swapchain starvation.
245
+
VKD3D_SWAPCHAIN_LATENCY_FRAMES = "3";
246
+
# To expose vram info from radv
247
+
WINEDLLOVERRIDES = "dxgi=n";
248
+
# Don't wait for buffers to idle on the client side before sending them to gamescope
249
+
vk_xwayland_wait_ready = "false";
250
+
# Temporary crutch until dummy plane interactions / etc are figured out
251
+
GAMESCOPE_DISABLE_ASYNC_FLIPS = "1";
254
+
# optionally enable 32bit pulseaudio support if pulseaudio is enabled
255
+
services.pulseaudio.support32Bit = config.services.pulseaudio.enable;
256
+
services.pipewire.alsa.support32Bit = config.services.pipewire.alsa.enable;
258
+
hardware.steam-hardware.enable = true;
260
+
services.inputplumber.enable = lib.mkDefault cfg.inputplumber.enable;
261
+
services.powerstation.enable = lib.mkDefault cfg.powerstation.enable;
263
+
environment.pathsToLink = [ "/share" ];
265
+
environment.systemPackages = [
267
+
] ++ lib.optional cfg.gamescopeSession.enable opengamepadui-gamescope;
270
+
meta.maintainers = with lib.maintainers; [ shadowapex ];