at master 9.7 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 cfg = config.programs.opengamepadui; 10 gamescopeCfg = config.programs.gamescope; 11 12 opengamepadui-gamescope = 13 let 14 exports = lib.mapAttrsToList (n: v: "export ${n}=${v}") cfg.gamescopeSession.env; 15 in 16 # Based on gamescope-session-plus from ChimeraOS 17 pkgs.writeShellScriptBin "opengamepadui-gamescope" '' 18 ${builtins.concatStringsSep "\n" exports} 19 20 # Enable Mangoapp 21 export MANGOHUD_CONFIGFILE=$(mktemp /tmp/mangohud.XXXXXXXX) 22 export RADV_FORCE_VRS_CONFIG_FILE=$(mktemp /tmp/radv_vrs.XXXXXXXX) 23 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" 27 28 # Make path to gamescope mode save file. 29 mkdir -p "$(dirname "$GAMESCOPE_MODE_SAVE_FILE")" 30 touch "$GAMESCOPE_MODE_SAVE_FILE" 31 32 # Make path to Gamescope edid patched file. 33 mkdir -p "$(dirname "$GAMESCOPE_PATCHED_EDID_FILE")" 34 touch "$GAMESCOPE_PATCHED_EDID_FILE" 35 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" 41 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" 46 47 # To play nice with the short term callback-based limiter for now 48 export GAMESCOPE_LIMITER_FILE=$(mktemp /tmp/gamescope-limiter.XXXXXXXX) 49 50 ulimit -n 524288 51 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}" 57 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?)" 61 exit 0 62 fi 63 64 export GAMESCOPE_STATS="$stats" 65 mkfifo -- "$stats" 66 mkfifo -- "$socket" 67 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 & 71 gamescope_pid="$!" 72 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" 76 # We're done! 77 else 78 echo "gamescope failed" 79 kill -9 "$gamescope_pid" 80 wait -n "$gamescope_pid" 81 exit 1 82 # Systemd or Session manager will have to restart session 83 fi 84 85 # If we have mangoapp binary start it 86 if command -v mangoapp >/dev/null; then 87 (while true; do 88 sleep 1 89 mangoapp >"$HOME"/.mangoapp-stdout.log 2>&1 90 done) & 91 fi 92 93 # Start OpenGamepadUI 94 opengamepadui ${lib.escapeShellArgs cfg.args} 95 96 # When the client exits, kill gamescope nicely 97 kill $gamescope_pid 98 ''; 99 100 gamescopeSessionFile = 101 (pkgs.writeTextDir "share/wayland-sessions/opengamepadui.desktop" '' 102 [Desktop Entry] 103 Name=opengamepadui 104 Comment=OpenGamepadUI Session 105 Exec=${opengamepadui-gamescope}/bin/opengamepadui-gamescope 106 Type=Application 107 '').overrideAttrs 108 (_: { 109 passthru.providedSessions = [ "opengamepadui" ]; 110 }); 111in 112{ 113 options.programs.opengamepadui = { 114 enable = lib.mkEnableOption "opengamepadui"; 115 116 args = lib.mkOption { 117 type = lib.types.listOf lib.types.str; 118 default = [ ]; 119 description = '' 120 Arguments to be passed to OpenGamepadUI 121 ''; 122 }; 123 124 package = lib.mkPackageOption pkgs "OpenGamepadUI" { 125 default = [ "opengamepadui" ]; 126 }; 127 128 extraPackages = lib.mkOption { 129 type = lib.types.listOf lib.types.package; 130 default = [ ]; 131 example = lib.literalExpression '' 132 with pkgs; [ 133 gamescope 134 ] 135 ''; 136 description = '' 137 Additional packages to add to the OpenGamepadUI environment. 138 ''; 139 }; 140 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 ]"; 146 description = '' 147 Font packages to use in OpenGamepadUI. 148 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. 150 ''; 151 }; 152 153 gamescopeSession = lib.mkOption { 154 description = "Run a GameScope driven OpenGamepadUI session from your display-manager"; 155 default = { }; 156 type = lib.types.submodule { 157 options = { 158 enable = lib.mkEnableOption "GameScope Session"; 159 args = lib.mkOption { 160 type = lib.types.listOf lib.types.str; 161 default = [ 162 "--prefer-output" 163 "*,eDP-1" 164 "--xwayland-count" 165 "2" 166 "--default-touch-mode" 167 "4" 168 "--hide-cursor-delay" 169 "3000" 170 "--fade-out-duration" 171 "200" 172 "--steam" 173 ]; 174 description = '' 175 Arguments to be passed to GameScope for the session. 176 ''; 177 }; 178 179 env = lib.mkOption { 180 type = lib.types.attrsOf lib.types.str; 181 default = { }; 182 description = '' 183 Environmental variables to be passed to GameScope for the session. 184 ''; 185 }; 186 }; 187 }; 188 }; 189 190 inputplumber.enable = lib.mkEnableOption '' 191 Run InputPlumber service for input management and gamepad configuration. 192 ''; 193 194 powerstation.enable = lib.mkEnableOption '' 195 Run PowerStation service for TDP control and performance settings. 196 ''; 197 }; 198 199 config = lib.mkIf cfg.enable { 200 hardware.graphics = { 201 # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932 202 enable = true; 203 enable32Bit = pkgs.stdenv.hostPlatform.isx86_64; 204 }; 205 206 security.wrappers = lib.mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) { 207 # needed or steam plugin fails 208 bwrap = { 209 owner = "root"; 210 group = "root"; 211 source = lib.getExe pkgs.bubblewrap; 212 setuid = true; 213 }; 214 }; 215 216 programs.opengamepadui.extraPackages = cfg.fontPackages; 217 218 programs.gamescope.enable = true; 219 services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [ 220 gamescopeSessionFile 221 ]; 222 223 programs.opengamepadui.gamescopeSession.env = { 224 # Fix intel color corruption 225 # might come with some performance degradation but is better than a corrupted 226 # color image 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"; 252 }; 253 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; 257 258 hardware.steam-hardware.enable = true; 259 260 services.inputplumber.enable = lib.mkDefault cfg.inputplumber.enable; 261 services.powerstation.enable = lib.mkDefault cfg.powerstation.enable; 262 263 environment.pathsToLink = [ "/share" ]; 264 265 environment.systemPackages = [ 266 cfg.package 267 ] 268 ++ lib.optional cfg.gamescopeSession.enable opengamepadui-gamescope; 269 }; 270 271 meta.maintainers = with lib.maintainers; [ shadowapex ]; 272}