1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.programs.steam;
7 gamescopeCfg = config.programs.gamescope;
8
9 steam-gamescope = let
10 exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env);
11 in
12 pkgs.writeShellScriptBin "steam-gamescope" ''
13 ${builtins.concatStringsSep "\n" exports}
14 gamescope --steam ${toString cfg.gamescopeSession.args} -- steam -tenfoot -pipewire-dmabuf
15 '';
16
17 gamescopeSessionFile =
18 (pkgs.writeTextDir "share/wayland-sessions/steam.desktop" ''
19 [Desktop Entry]
20 Name=Steam
21 Comment=A digital distribution platform
22 Exec=${steam-gamescope}/bin/steam-gamescope
23 Type=Application
24 '').overrideAttrs (_: { passthru.providedSessions = [ "steam" ]; });
25in {
26 options.programs.steam = {
27 enable = mkEnableOption (lib.mdDoc "steam");
28
29 package = mkOption {
30 type = types.package;
31 default = pkgs.steam;
32 defaultText = literalExpression "pkgs.steam";
33 example = literalExpression ''
34 pkgs.steam-small.override {
35 extraEnv = {
36 MANGOHUD = true;
37 OBS_VKCAPTURE = true;
38 RADV_TEX_ANISO = 16;
39 };
40 extraLibraries = p: with p; [
41 atk
42 ];
43 }
44 '';
45 apply = steam: steam.override (prev: {
46 extraLibraries = pkgs: let
47 prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
48 additionalLibs = with config.hardware.opengl;
49 if pkgs.stdenv.hostPlatform.is64bit
50 then [ package ] ++ extraPackages
51 else [ package32 ] ++ extraPackages32;
52 in prevLibs ++ additionalLibs;
53 } // optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice)
54 {
55 buildFHSEnv = pkgs.buildFHSEnv.override {
56 # use the setuid wrapped bubblewrap
57 bubblewrap = "${config.security.wrapperDir}/..";
58 };
59 });
60 description = lib.mdDoc ''
61 The Steam package to use. Additional libraries are added from the system
62 configuration to ensure graphics work properly.
63
64 Use this option to customise the Steam package rather than adding your
65 custom Steam to {option}`environment.systemPackages` yourself.
66 '';
67 };
68
69 remotePlay.openFirewall = mkOption {
70 type = types.bool;
71 default = false;
72 description = lib.mdDoc ''
73 Open ports in the firewall for Steam Remote Play.
74 '';
75 };
76
77 dedicatedServer.openFirewall = mkOption {
78 type = types.bool;
79 default = false;
80 description = lib.mdDoc ''
81 Open ports in the firewall for Source Dedicated Server.
82 '';
83 };
84
85 gamescopeSession = mkOption {
86 description = mdDoc "Run a GameScope driven Steam session from your display-manager";
87 default = {};
88 type = types.submodule {
89 options = {
90 enable = mkEnableOption (mdDoc "GameScope Session");
91 args = mkOption {
92 type = types.listOf types.string;
93 default = [ ];
94 description = mdDoc ''
95 Arguments to be passed to GameScope for the session.
96 '';
97 };
98
99 env = mkOption {
100 type = types.attrsOf types.string;
101 default = { };
102 description = mdDoc ''
103 Environmental variables to be passed to GameScope for the session.
104 '';
105 };
106 };
107 };
108 };
109 };
110
111 config = mkIf cfg.enable {
112 hardware.opengl = { # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
113 enable = true;
114 driSupport = true;
115 driSupport32Bit = true;
116 };
117
118 security.wrappers = mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
119 # needed or steam fails
120 bwrap = {
121 owner = "root";
122 group = "root";
123 source = "${pkgs.bubblewrap}/bin/bwrap";
124 setuid = true;
125 };
126 };
127
128 programs.gamescope.enable = mkDefault cfg.gamescopeSession.enable;
129 services.xserver.displayManager.sessionPackages = mkIf cfg.gamescopeSession.enable [ gamescopeSessionFile ];
130
131 # optionally enable 32bit pulseaudio support if pulseaudio is enabled
132 hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
133
134 hardware.steam-hardware.enable = true;
135
136 environment.systemPackages = [
137 cfg.package
138 cfg.package.run
139 ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope;
140
141 networking.firewall = lib.mkMerge [
142 (mkIf cfg.remotePlay.openFirewall {
143 allowedTCPPorts = [ 27036 ];
144 allowedUDPPortRanges = [ { from = 27031; to = 27036; } ];
145 })
146
147 (mkIf cfg.dedicatedServer.openFirewall {
148 allowedTCPPorts = [ 27015 ]; # SRCDS Rcon port
149 allowedUDPPorts = [ 27015 ]; # Gameplay traffic
150 })
151 ];
152 };
153
154 meta.maintainers = with maintainers; [ mkg20001 ];
155}