1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.programs.steam;
10 gamescopeCfg = config.programs.gamescope;
11
12 extraCompatPaths = lib.makeSearchPathOutput "steamcompattool" "" cfg.extraCompatPackages;
13
14 steam-gamescope =
15 let
16 exports = builtins.attrValues (
17 builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env
18 );
19 in
20 pkgs.writeShellScriptBin "steam-gamescope" ''
21 ${builtins.concatStringsSep "\n" exports}
22 gamescope --steam ${builtins.toString cfg.gamescopeSession.args} -- steam ${builtins.toString cfg.gamescopeSession.steamArgs}
23 '';
24
25 gamescopeSessionFile =
26 (pkgs.writeTextDir "share/wayland-sessions/steam.desktop" ''
27 [Desktop Entry]
28 Name=Steam
29 Comment=A digital distribution platform
30 Exec=${steam-gamescope}/bin/steam-gamescope
31 Type=Application
32 '').overrideAttrs
33 (_: {
34 passthru.providedSessions = [ "steam" ];
35 });
36in
37{
38 options.programs.steam = {
39 enable = lib.mkEnableOption "steam";
40
41 package = lib.mkOption {
42 type = lib.types.package;
43 default = pkgs.steam;
44 defaultText = lib.literalExpression "pkgs.steam";
45 example = lib.literalExpression ''
46 pkgs.steam.override {
47 extraEnv = {
48 MANGOHUD = true;
49 OBS_VKCAPTURE = true;
50 RADV_TEX_ANISO = 16;
51 };
52 extraLibraries = p: with p; [
53 atk
54 ];
55 }
56 '';
57 apply =
58 steam:
59 steam.override (
60 prev:
61 {
62 extraEnv =
63 (lib.optionalAttrs (cfg.extraCompatPackages != [ ]) {
64 STEAM_EXTRA_COMPAT_TOOLS_PATHS = extraCompatPaths;
65 })
66 // (lib.optionalAttrs cfg.extest.enable {
67 LD_PRELOAD = "${pkgs.pkgsi686Linux.extest}/lib/libextest.so";
68 })
69 // (prev.extraEnv or { });
70 extraLibraries =
71 pkgs:
72 let
73 prevLibs = if prev ? extraLibraries then prev.extraLibraries pkgs else [ ];
74 additionalLibs =
75 with config.hardware.graphics;
76 if pkgs.stdenv.hostPlatform.is64bit then
77 [ package ] ++ extraPackages
78 else
79 [ package32 ] ++ extraPackages32;
80 in
81 prevLibs ++ additionalLibs;
82 extraPkgs = p: (cfg.extraPackages ++ lib.optionals (prev ? extraPkgs) (prev.extraPkgs p));
83 }
84 // lib.optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
85 buildFHSEnv = pkgs.buildFHSEnv.override {
86 # use the setuid wrapped bubblewrap
87 bubblewrap = "${config.security.wrapperDir}/..";
88 };
89 }
90 );
91 description = ''
92 The Steam package to use. Additional libraries are added from the system
93 configuration to ensure graphics work properly.
94
95 Use this option to customise the Steam package rather than adding your
96 custom Steam to {option}`environment.systemPackages` yourself.
97 '';
98 };
99
100 extraPackages = lib.mkOption {
101 type = lib.types.listOf lib.types.package;
102 default = [ ];
103 example = lib.literalExpression ''
104 with pkgs; [
105 gamescope
106 ]
107 '';
108 description = ''
109 Additional packages to add to the Steam environment.
110 '';
111 };
112
113 extraCompatPackages = lib.mkOption {
114 type = lib.types.listOf lib.types.package;
115 default = [ ];
116 example = lib.literalExpression ''
117 with pkgs; [
118 proton-ge-bin
119 ]
120 '';
121 description = ''
122 Extra packages to be used as compatibility tools for Steam on Linux. Packages will be included
123 in the `STEAM_EXTRA_COMPAT_TOOLS_PATHS` environmental variable. For more information see
124 https://github.com/ValveSoftware/steam-for-linux/issues/6310.
125
126 These packages must be Steam compatibility tools that have a `steamcompattool` output.
127 '';
128 };
129
130 fontPackages = lib.mkOption {
131 type = lib.types.listOf lib.types.package;
132 # `fonts.packages` is a list of paths now, filter out which are not packages
133 default = builtins.filter lib.types.package.check config.fonts.packages;
134 defaultText = lib.literalExpression "builtins.filter lib.types.package.check config.fonts.packages";
135 example = lib.literalExpression "with pkgs; [ source-han-sans ]";
136 description = ''
137 Font packages to use in Steam.
138
139 Defaults to system fonts, but could be overridden to use other fonts — useful for users who would like to customize CJK fonts used in Steam. According to the [upstream issue](https://github.com/ValveSoftware/steam-for-linux/issues/10422#issuecomment-1944396010), Steam only follows the per-user fontconfig configuration.
140 '';
141 };
142
143 remotePlay.openFirewall = lib.mkOption {
144 type = lib.types.bool;
145 default = false;
146 description = ''
147 Open ports in the firewall for Steam Remote Play.
148 '';
149 };
150
151 dedicatedServer.openFirewall = lib.mkOption {
152 type = lib.types.bool;
153 default = false;
154 description = ''
155 Open ports in the firewall for Source Dedicated Server.
156 '';
157 };
158
159 localNetworkGameTransfers.openFirewall = lib.mkOption {
160 type = lib.types.bool;
161 default = false;
162 description = ''
163 Open ports in the firewall for Steam Local Network Game Transfers.
164 '';
165 };
166
167 gamescopeSession = lib.mkOption {
168 description = "Run a GameScope driven Steam session from your display-manager";
169 default = { };
170 type = lib.types.submodule {
171 options = {
172 enable = lib.mkEnableOption "GameScope Session";
173 args = lib.mkOption {
174 type = lib.types.listOf lib.types.str;
175 default = [ ];
176 description = ''
177 Arguments to be passed to GameScope for the session.
178 '';
179 };
180
181 env = lib.mkOption {
182 type = lib.types.attrsOf lib.types.str;
183 default = { };
184 description = ''
185 Environmental variables to be passed to GameScope for the session.
186 '';
187 };
188
189 steamArgs = lib.mkOption {
190 type = lib.types.listOf lib.types.str;
191 default = [
192 "-tenfoot"
193 "-pipewire-dmabuf"
194 ];
195 description = ''
196 Arguments to be passed to Steam for the session.
197 '';
198 };
199 };
200 };
201 };
202
203 extest.enable = lib.mkEnableOption ''
204 Load the extest library into Steam, to translate X11 input events to
205 uinput events (e.g. for using Steam Input on Wayland)
206 '';
207
208 protontricks = {
209 enable = lib.mkEnableOption "protontricks, a simple wrapper for running Winetricks commands for Proton-enabled games";
210 package = lib.mkPackageOption pkgs "protontricks" { };
211 };
212 };
213
214 config = lib.mkIf cfg.enable {
215 hardware.graphics = {
216 # this fixes the "glXChooseVisual failed" bug, context: https://github.com/NixOS/nixpkgs/issues/47932
217 enable = true;
218 enable32Bit = true;
219 };
220
221 security.wrappers = lib.mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
222 # needed or steam fails
223 bwrap = {
224 owner = "root";
225 group = "root";
226 source = "${pkgs.bubblewrap}/bin/bwrap";
227 setuid = true;
228 };
229 };
230
231 programs.steam.extraPackages = cfg.fontPackages;
232
233 programs.gamescope.enable = lib.mkDefault cfg.gamescopeSession.enable;
234 services.displayManager.sessionPackages = lib.mkIf cfg.gamescopeSession.enable [
235 gamescopeSessionFile
236 ];
237
238 # enable 32bit pulseaudio/pipewire support if needed
239 services.pulseaudio.support32Bit = config.services.pulseaudio.enable;
240 services.pipewire.alsa.support32Bit = config.services.pipewire.alsa.enable;
241
242 hardware.steam-hardware.enable = true;
243
244 environment.systemPackages = [
245 cfg.package
246 cfg.package.run
247 ]
248 ++ lib.optional cfg.gamescopeSession.enable steam-gamescope
249 ++ lib.optional cfg.protontricks.enable (
250 cfg.protontricks.package.override { inherit extraCompatPaths; }
251 );
252
253 networking.firewall = lib.mkMerge [
254 (lib.mkIf (cfg.remotePlay.openFirewall || cfg.localNetworkGameTransfers.openFirewall) {
255 allowedUDPPorts = [ 27036 ]; # Peer discovery
256 })
257
258 (lib.mkIf cfg.remotePlay.openFirewall {
259 allowedTCPPorts = [ 27036 ];
260 allowedUDPPortRanges = [
261 {
262 from = 27031;
263 to = 27035;
264 }
265 ];
266 })
267
268 (lib.mkIf cfg.dedicatedServer.openFirewall {
269 allowedTCPPorts = [ 27015 ]; # SRCDS Rcon port
270 allowedUDPPorts = [ 27015 ]; # Gameplay traffic
271 })
272
273 (lib.mkIf cfg.localNetworkGameTransfers.openFirewall {
274 allowedTCPPorts = [ 27040 ]; # Data transfers
275 })
276 ];
277 };
278
279 meta.maintainers = lib.teams.steam.members;
280}